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
|
@@ -9,6 +9,7 @@ from typing import Any, Final, Generic, TypeVar
|
|
|
9
9
|
from botocore.exceptions import ClientError
|
|
10
10
|
|
|
11
11
|
from localstack import config
|
|
12
|
+
from localstack.aws.api.cloudformation import ResourceStatus
|
|
12
13
|
from localstack.aws.api.ec2 import AvailabilityZoneList, DescribeAvailabilityZonesResult
|
|
13
14
|
from localstack.aws.connect import connect_to
|
|
14
15
|
from localstack.services.cloudformation.engine.v2.change_set_model import (
|
|
@@ -29,6 +30,7 @@ from localstack.services.cloudformation.engine.v2.change_set_model import (
|
|
|
29
30
|
NodeProperties,
|
|
30
31
|
NodeProperty,
|
|
31
32
|
NodeResource,
|
|
33
|
+
NodeResources,
|
|
32
34
|
NodeTemplate,
|
|
33
35
|
Nothing,
|
|
34
36
|
NothingType,
|
|
@@ -44,6 +46,7 @@ from localstack.services.cloudformation.engine.v2.change_set_model_visitor impor
|
|
|
44
46
|
ChangeSetModelVisitor,
|
|
45
47
|
)
|
|
46
48
|
from localstack.services.cloudformation.engine.v2.resolving import (
|
|
49
|
+
REGEX_DYNAMIC_REF,
|
|
47
50
|
extract_dynamic_reference,
|
|
48
51
|
perform_dynamic_reference_lookup,
|
|
49
52
|
)
|
|
@@ -52,7 +55,9 @@ from localstack.services.cloudformation.stores import (
|
|
|
52
55
|
exports_map,
|
|
53
56
|
)
|
|
54
57
|
from localstack.services.cloudformation.v2.entities import ChangeSet
|
|
58
|
+
from localstack.services.cloudformation.v2.types import ResolvedResource
|
|
55
59
|
from localstack.utils.aws.arns import get_partition
|
|
60
|
+
from localstack.utils.numbers import to_number
|
|
56
61
|
from localstack.utils.objects import get_value_from_path
|
|
57
62
|
from localstack.utils.run import to_str
|
|
58
63
|
from localstack.utils.strings import to_bytes
|
|
@@ -73,7 +78,11 @@ _PSEUDO_PARAMETERS: Final[set[str]] = {
|
|
|
73
78
|
|
|
74
79
|
TBefore = TypeVar("TBefore")
|
|
75
80
|
TAfter = TypeVar("TAfter")
|
|
81
|
+
_T = TypeVar("_T")
|
|
76
82
|
|
|
83
|
+
REGEX_OUTPUT_APIGATEWAY = re.compile(
|
|
84
|
+
rf"^(https?://.+\.execute-api\.)(?:[^-]+-){{2,3}}\d\.(amazonaws\.com|{_AWS_URL_SUFFIX})/?(.*)$"
|
|
85
|
+
)
|
|
77
86
|
MOCKED_REFERENCE = "unknown"
|
|
78
87
|
|
|
79
88
|
VALID_LOGICAL_RESOURCE_ID_RE = re.compile(r"^[A-Za-z0-9]+$")
|
|
@@ -113,6 +122,7 @@ class PreprocResource:
|
|
|
113
122
|
properties: PreprocProperties
|
|
114
123
|
depends_on: list[str] | None
|
|
115
124
|
requires_replacement: bool
|
|
125
|
+
status: ResourceStatus | None
|
|
116
126
|
|
|
117
127
|
def __init__(
|
|
118
128
|
self,
|
|
@@ -123,6 +133,7 @@ class PreprocResource:
|
|
|
123
133
|
properties: PreprocProperties,
|
|
124
134
|
depends_on: list[str] | None,
|
|
125
135
|
requires_replacement: bool,
|
|
136
|
+
status: ResourceStatus | None = None,
|
|
126
137
|
):
|
|
127
138
|
self.logical_id = logical_id
|
|
128
139
|
self.physical_resource_id = physical_resource_id
|
|
@@ -131,6 +142,7 @@ class PreprocResource:
|
|
|
131
142
|
self.properties = properties
|
|
132
143
|
self.depends_on = depends_on
|
|
133
144
|
self.requires_replacement = requires_replacement
|
|
145
|
+
self.status = status
|
|
134
146
|
|
|
135
147
|
@staticmethod
|
|
136
148
|
def _compare_conditions(c1: bool, c2: bool):
|
|
@@ -215,6 +227,8 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
215
227
|
def process(self) -> None:
|
|
216
228
|
self._setup_runtime_cache()
|
|
217
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)
|
|
218
232
|
self.visit(node_template)
|
|
219
233
|
self._save_runtime_cache()
|
|
220
234
|
|
|
@@ -260,16 +274,16 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
260
274
|
f"No deployed instances of resource '{resource_logical_id}' were found"
|
|
261
275
|
)
|
|
262
276
|
properties = resolved_resource.get("Properties", {})
|
|
263
|
-
# support structured properties, e.g. NestedStack.Outputs.OutputName
|
|
277
|
+
# TODO support structured properties, e.g. NestedStack.Outputs.OutputName
|
|
264
278
|
property_value: Any | None = get_value_from_path(properties, property_name)
|
|
265
279
|
|
|
266
280
|
if property_value:
|
|
267
|
-
if not isinstance(property_value, str):
|
|
268
|
-
#
|
|
269
|
-
#
|
|
270
|
-
#
|
|
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.
|
|
271
285
|
raise RuntimeError(
|
|
272
|
-
f"Accessing property '{property_name}' from '{resource_logical_id}' resulted in a non-string value"
|
|
286
|
+
f"Accessing property '{property_name}' from '{resource_logical_id}' resulted in a non-string value nor list"
|
|
273
287
|
)
|
|
274
288
|
return property_value
|
|
275
289
|
elif config.CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES:
|
|
@@ -395,10 +409,63 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
395
409
|
return PreprocEntityDelta(before=before, after=after)
|
|
396
410
|
delta = super().visit(change_set_entity=change_set_entity)
|
|
397
411
|
if isinstance(delta, PreprocEntityDelta):
|
|
412
|
+
delta = self._maybe_perform_replacements(delta)
|
|
398
413
|
self._before_cache[entity_scope] = delta.before
|
|
399
414
|
self._after_cache[entity_scope] = delta.after
|
|
400
415
|
return delta
|
|
401
416
|
|
|
417
|
+
def _maybe_perform_replacements(self, delta: PreprocEntityDelta) -> PreprocEntityDelta:
|
|
418
|
+
delta = self._maybe_perform_static_replacements(delta)
|
|
419
|
+
delta = self._maybe_perform_dynamic_replacements(delta)
|
|
420
|
+
return delta
|
|
421
|
+
|
|
422
|
+
def _maybe_perform_static_replacements(self, delta: PreprocEntityDelta) -> PreprocEntityDelta:
|
|
423
|
+
return self._maybe_perform_on_delta(delta, self._perform_static_replacements)
|
|
424
|
+
|
|
425
|
+
def _maybe_perform_dynamic_replacements(self, delta: PreprocEntityDelta) -> PreprocEntityDelta:
|
|
426
|
+
return self._maybe_perform_on_delta(delta, self._perform_dynamic_replacements)
|
|
427
|
+
|
|
428
|
+
def _maybe_perform_on_delta(
|
|
429
|
+
self, delta: PreprocEntityDelta | None, f: Callable[[_T], _T]
|
|
430
|
+
) -> PreprocEntityDelta | None:
|
|
431
|
+
if isinstance(delta.before, str):
|
|
432
|
+
delta.before = f(delta.before)
|
|
433
|
+
if isinstance(delta.after, str):
|
|
434
|
+
delta.after = f(delta.after)
|
|
435
|
+
return delta
|
|
436
|
+
|
|
437
|
+
def _perform_dynamic_replacements(self, value: _T) -> _T:
|
|
438
|
+
if not isinstance(value, str):
|
|
439
|
+
return value
|
|
440
|
+
|
|
441
|
+
if dynamic_ref := extract_dynamic_reference(value):
|
|
442
|
+
new_value = perform_dynamic_reference_lookup(
|
|
443
|
+
reference=dynamic_ref,
|
|
444
|
+
account_id=self._change_set.account_id,
|
|
445
|
+
region_name=self._change_set.region_name,
|
|
446
|
+
)
|
|
447
|
+
if new_value:
|
|
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)
|
|
453
|
+
|
|
454
|
+
return value
|
|
455
|
+
|
|
456
|
+
@staticmethod
|
|
457
|
+
def _perform_static_replacements(value: str) -> str:
|
|
458
|
+
api_match = REGEX_OUTPUT_APIGATEWAY.match(value)
|
|
459
|
+
if api_match and value not in config.CFN_STRING_REPLACEMENT_DENY_LIST:
|
|
460
|
+
prefix = api_match[1]
|
|
461
|
+
host = api_match[2]
|
|
462
|
+
path = api_match[3]
|
|
463
|
+
port = localstack_host().port
|
|
464
|
+
value = f"{prefix}{host}:{port}/{path}"
|
|
465
|
+
return value
|
|
466
|
+
|
|
467
|
+
return value
|
|
468
|
+
|
|
402
469
|
def _cached_apply(
|
|
403
470
|
self, scope: Scope, arguments_delta: PreprocEntityDelta, resolver: Callable[[Any], Any]
|
|
404
471
|
) -> PreprocEntityDelta:
|
|
@@ -447,6 +514,9 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
447
514
|
|
|
448
515
|
return PreprocEntityDelta(before=before, after=after)
|
|
449
516
|
|
|
517
|
+
def visit_node_property(self, node_property: NodeProperty) -> PreprocEntityDelta:
|
|
518
|
+
return self.visit(node_property.value)
|
|
519
|
+
|
|
450
520
|
def visit_terminal_value_modified(
|
|
451
521
|
self, terminal_value_modified: TerminalValueModified
|
|
452
522
|
) -> PreprocEntityDelta:
|
|
@@ -486,9 +556,9 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
486
556
|
delta: PreprocEntityDelta = self.visit(change_set_entity=change_set_entity)
|
|
487
557
|
delta_before = delta.before
|
|
488
558
|
delta_after = delta.after
|
|
489
|
-
if not is_nothing(before) and not is_nothing(delta_before)
|
|
559
|
+
if not is_nothing(before) and not is_nothing(delta_before):
|
|
490
560
|
before[name] = delta_before
|
|
491
|
-
if not is_nothing(after) and not is_nothing(delta_after)
|
|
561
|
+
if not is_nothing(after) and not is_nothing(delta_after):
|
|
492
562
|
after[name] = delta_after
|
|
493
563
|
return PreprocEntityDelta(before=before, after=after)
|
|
494
564
|
|
|
@@ -499,13 +569,49 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
499
569
|
arguments_list = arguments.split(".")
|
|
500
570
|
else:
|
|
501
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
|
+
|
|
502
578
|
logical_name_of_resource = arguments_list[0]
|
|
503
|
-
attribute_name = arguments_list[1]
|
|
579
|
+
attribute_name = ".".join(arguments_list[1:])
|
|
504
580
|
|
|
505
581
|
node_resource = self._get_node_resource_for(
|
|
506
582
|
resource_name=logical_name_of_resource,
|
|
507
583
|
node_template=self._change_set.update_model.node_template,
|
|
508
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
|
+
|
|
509
615
|
node_property: NodeProperty | None = self._get_node_property_for(
|
|
510
616
|
property_name=attribute_name, node_resource=node_resource
|
|
511
617
|
)
|
|
@@ -513,19 +619,7 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
513
619
|
# The property is statically defined in the template and its value can be computed.
|
|
514
620
|
property_delta = self.visit(node_property)
|
|
515
621
|
value = property_delta.before if select_before else property_delta.after
|
|
516
|
-
|
|
517
|
-
# The property is not statically defined and must therefore be available in
|
|
518
|
-
# the properties deployed set.
|
|
519
|
-
if select_before:
|
|
520
|
-
value = self._before_deployed_property_value_of(
|
|
521
|
-
resource_logical_id=logical_name_of_resource,
|
|
522
|
-
property_name=attribute_name,
|
|
523
|
-
)
|
|
524
|
-
else:
|
|
525
|
-
value = self._after_deployed_property_value_of(
|
|
526
|
-
resource_logical_id=logical_name_of_resource,
|
|
527
|
-
property_name=attribute_name,
|
|
528
|
-
)
|
|
622
|
+
|
|
529
623
|
return value
|
|
530
624
|
|
|
531
625
|
def visit_node_intrinsic_function_fn_get_att(
|
|
@@ -554,6 +648,12 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
554
648
|
return args[0] == args[1]
|
|
555
649
|
|
|
556
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
|
+
|
|
557
657
|
delta = self._cached_apply(
|
|
558
658
|
scope=node_intrinsic_function.scope,
|
|
559
659
|
arguments_delta=arguments_delta,
|
|
@@ -630,8 +730,12 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
630
730
|
def visit_node_intrinsic_function_fn_not(
|
|
631
731
|
self, node_intrinsic_function: NodeIntrinsicFunction
|
|
632
732
|
) -> PreprocEntityDelta:
|
|
633
|
-
def _compute_fn_not(arg: bool) -> bool:
|
|
634
|
-
|
|
733
|
+
def _compute_fn_not(arg: list[bool] | bool) -> bool:
|
|
734
|
+
# Is the argument ever a lone boolean?
|
|
735
|
+
if isinstance(arg, list):
|
|
736
|
+
return not arg[0]
|
|
737
|
+
else:
|
|
738
|
+
return not arg
|
|
635
739
|
|
|
636
740
|
arguments_delta = self.visit(node_intrinsic_function.arguments)
|
|
637
741
|
delta = self._cached_apply(
|
|
@@ -779,13 +883,27 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
779
883
|
):
|
|
780
884
|
# TODO: add further support for schema validation
|
|
781
885
|
def _compute_fn_select(args: list[Any]) -> Any:
|
|
782
|
-
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
|
+
|
|
783
891
|
if not isinstance(values, list) or not values:
|
|
784
|
-
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
|
+
|
|
785
902
|
values_len = len(values)
|
|
786
|
-
index
|
|
787
|
-
|
|
788
|
-
|
|
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
|
+
)
|
|
789
907
|
selection = values[index]
|
|
790
908
|
return selection
|
|
791
909
|
|
|
@@ -812,6 +930,17 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
812
930
|
return split_string
|
|
813
931
|
|
|
814
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
|
+
|
|
815
944
|
delta = self._cached_apply(
|
|
816
945
|
scope=node_intrinsic_function.scope,
|
|
817
946
|
arguments_delta=arguments_delta,
|
|
@@ -911,6 +1040,10 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
911
1040
|
return PreprocEntityDelta(before=before_parameters, after=after_parameters)
|
|
912
1041
|
|
|
913
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
|
+
)
|
|
914
1047
|
dynamic_value = node_parameter.dynamic_value
|
|
915
1048
|
dynamic_delta = self.visit(dynamic_value)
|
|
916
1049
|
|
|
@@ -924,8 +1057,11 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
924
1057
|
|
|
925
1058
|
def _resolve_parameter_type(value: str, type_: str) -> Any:
|
|
926
1059
|
match type_:
|
|
927
|
-
case "List<String>":
|
|
1060
|
+
case "List<String>" | "CommaDelimitedList":
|
|
928
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)
|
|
929
1065
|
return value
|
|
930
1066
|
|
|
931
1067
|
if not is_nothing(after):
|
|
@@ -942,11 +1078,17 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
942
1078
|
return delta
|
|
943
1079
|
|
|
944
1080
|
def _resource_physical_resource_id_from(
|
|
945
|
-
self, logical_resource_id: str, resolved_resources: dict
|
|
946
|
-
) -> str:
|
|
1081
|
+
self, logical_resource_id: str, resolved_resources: dict[str, ResolvedResource]
|
|
1082
|
+
) -> str | None:
|
|
947
1083
|
# TODO: typing around resolved resources is needed and should be reflected here.
|
|
948
1084
|
resolved_resource = resolved_resources.get(logical_resource_id, {})
|
|
949
|
-
|
|
1085
|
+
if resolved_resource.get("ResourceStatus") not in {
|
|
1086
|
+
ResourceStatus.CREATE_COMPLETE,
|
|
1087
|
+
ResourceStatus.UPDATE_COMPLETE,
|
|
1088
|
+
}:
|
|
1089
|
+
return None
|
|
1090
|
+
|
|
1091
|
+
physical_resource_id = resolved_resource.get("PhysicalResourceId")
|
|
950
1092
|
if not isinstance(physical_resource_id, str):
|
|
951
1093
|
raise RuntimeError(f"No PhysicalResourceId found for resource '{logical_resource_id}'")
|
|
952
1094
|
return physical_resource_id
|
|
@@ -965,6 +1107,9 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
965
1107
|
self, node_intrinsic_function: NodeIntrinsicFunction
|
|
966
1108
|
) -> PreprocEntityDelta:
|
|
967
1109
|
def _compute_fn_ref(logical_id: str) -> PreprocEntityDelta:
|
|
1110
|
+
if logical_id == "AWS::NoValue":
|
|
1111
|
+
return Nothing
|
|
1112
|
+
|
|
968
1113
|
reference_delta: PreprocEntityDelta = self._resolve_reference(logical_id=logical_id)
|
|
969
1114
|
if isinstance(before := reference_delta.before, PreprocResource):
|
|
970
1115
|
reference_delta.before = before.physical_resource_id
|
|
@@ -1013,25 +1158,6 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
1013
1158
|
after.append(delta_after)
|
|
1014
1159
|
return PreprocEntityDelta(before=before, after=after)
|
|
1015
1160
|
|
|
1016
|
-
def visit_node_property(self, node_property: NodeProperty) -> PreprocEntityDelta:
|
|
1017
|
-
# TODO: what about other positions?
|
|
1018
|
-
value = self.visit(node_property.value)
|
|
1019
|
-
if not is_nothing(value.before):
|
|
1020
|
-
if dynamic_ref := extract_dynamic_reference(value.before):
|
|
1021
|
-
value.before = perform_dynamic_reference_lookup(
|
|
1022
|
-
reference=dynamic_ref,
|
|
1023
|
-
account_id=self._change_set.account_id,
|
|
1024
|
-
region_name=self._change_set.region_name,
|
|
1025
|
-
)
|
|
1026
|
-
if not is_nothing(value.after):
|
|
1027
|
-
if dynamic_ref := extract_dynamic_reference(value.after):
|
|
1028
|
-
value.after = perform_dynamic_reference_lookup(
|
|
1029
|
-
reference=dynamic_ref,
|
|
1030
|
-
account_id=self._change_set.account_id,
|
|
1031
|
-
region_name=self._change_set.region_name,
|
|
1032
|
-
)
|
|
1033
|
-
return value
|
|
1034
|
-
|
|
1035
1161
|
def visit_node_properties(
|
|
1036
1162
|
self, node_properties: NodeProperties
|
|
1037
1163
|
) -> PreprocEntityDelta[PreprocProperties, PreprocProperties]:
|
|
@@ -1077,6 +1203,20 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
1077
1203
|
after = after_delta.after
|
|
1078
1204
|
return PreprocEntityDelta(before=before, after=after)
|
|
1079
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
|
+
|
|
1080
1220
|
def visit_node_resource(
|
|
1081
1221
|
self, node_resource: NodeResource
|
|
1082
1222
|
) -> PreprocEntityDelta[PreprocResource, PreprocResource]:
|
|
@@ -1187,6 +1327,14 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
1187
1327
|
before: list[PreprocOutput] = []
|
|
1188
1328
|
after: list[PreprocOutput] = []
|
|
1189
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
|
+
|
|
1190
1338
|
output_delta: PreprocEntityDelta[PreprocOutput, PreprocOutput] = self.visit(node_output)
|
|
1191
1339
|
output_before = output_delta.before
|
|
1192
1340
|
output_after = output_delta.after
|
|
@@ -1216,3 +1364,8 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
1216
1364
|
resolver=_compute_fn_import_value,
|
|
1217
1365
|
)
|
|
1218
1366
|
return delta
|
|
1367
|
+
|
|
1368
|
+
def visit_node_intrinsic_function_fn_transform(
|
|
1369
|
+
self, node_intrinsic_function: NodeIntrinsicFunction
|
|
1370
|
+
):
|
|
1371
|
+
raise RuntimeError("Fn::Transform should have been handled by the Transformer")
|