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
|
@@ -1,24 +1,36 @@
|
|
|
1
1
|
import copy
|
|
2
|
+
import json
|
|
2
3
|
import logging
|
|
3
4
|
import os
|
|
5
|
+
import re
|
|
4
6
|
from typing import Any, Final, TypedDict
|
|
5
7
|
|
|
6
8
|
import boto3
|
|
7
|
-
|
|
9
|
+
import jsonpath_ng
|
|
10
|
+
from botocore.exceptions import ClientError, ParamValidationError
|
|
8
11
|
from samtranslator.translator.transform import transform as transform_sam
|
|
9
12
|
|
|
10
13
|
from localstack.aws.connect import connect_to
|
|
14
|
+
from localstack.services.cloudformation.engine.parameters import StackParameter
|
|
11
15
|
from localstack.services.cloudformation.engine.policy_loader import create_policy_loader
|
|
12
16
|
from localstack.services.cloudformation.engine.template_preparer import parse_template
|
|
13
17
|
from localstack.services.cloudformation.engine.transformers import (
|
|
14
18
|
FailedTransformationException,
|
|
15
|
-
|
|
19
|
+
ResolveRefsRecursivelyContext,
|
|
20
|
+
apply_language_extensions_transform,
|
|
16
21
|
)
|
|
17
22
|
from localstack.services.cloudformation.engine.v2.change_set_model import (
|
|
18
23
|
ChangeType,
|
|
24
|
+
FnTransform,
|
|
19
25
|
Maybe,
|
|
26
|
+
NodeForEach,
|
|
20
27
|
NodeGlobalTransform,
|
|
21
|
-
|
|
28
|
+
NodeIntrinsicFunction,
|
|
29
|
+
NodeIntrinsicFunctionFnTransform,
|
|
30
|
+
NodeProperties,
|
|
31
|
+
NodeProperty,
|
|
32
|
+
NodeResource,
|
|
33
|
+
NodeResources,
|
|
22
34
|
NodeTransform,
|
|
23
35
|
Nothing,
|
|
24
36
|
Scope,
|
|
@@ -27,10 +39,14 @@ from localstack.services.cloudformation.engine.v2.change_set_model import (
|
|
|
27
39
|
from localstack.services.cloudformation.engine.v2.change_set_model_preproc import (
|
|
28
40
|
ChangeSetModelPreproc,
|
|
29
41
|
PreprocEntityDelta,
|
|
42
|
+
PreprocProperties,
|
|
30
43
|
)
|
|
44
|
+
from localstack.services.cloudformation.engine.validations import ValidationError
|
|
31
45
|
from localstack.services.cloudformation.stores import get_cloudformation_store
|
|
32
46
|
from localstack.services.cloudformation.v2.entities import ChangeSet
|
|
47
|
+
from localstack.services.cloudformation.v2.types import EngineParameter, engine_parameter_value
|
|
33
48
|
from localstack.utils import testutil
|
|
49
|
+
from localstack.utils.strings import long_uid
|
|
34
50
|
|
|
35
51
|
LOG = logging.getLogger(__name__)
|
|
36
52
|
|
|
@@ -42,6 +58,20 @@ INCLUDE_TRANSFORM = "AWS::Include"
|
|
|
42
58
|
_SCOPE_TRANSFORM_TEMPLATE_OUTCOME: Final[Scope] = Scope("TRANSFORM_TEMPLATE_OUTCOME")
|
|
43
59
|
|
|
44
60
|
|
|
61
|
+
def engine_parameters_to_stack_parameters(
|
|
62
|
+
engine_parameters: dict[str, EngineParameter],
|
|
63
|
+
) -> dict[str, StackParameter]:
|
|
64
|
+
out = {}
|
|
65
|
+
for name, engine_param in engine_parameters.items():
|
|
66
|
+
out[name] = StackParameter(
|
|
67
|
+
ParameterKey=name,
|
|
68
|
+
ParameterValue=engine_parameter_value(engine_param),
|
|
69
|
+
ResolvedValue=engine_param.get("resolved_value"),
|
|
70
|
+
ParameterType=engine_param["type_"],
|
|
71
|
+
)
|
|
72
|
+
return out
|
|
73
|
+
|
|
74
|
+
|
|
45
75
|
# TODO: evaluate the use of subtypes to represent and validate types of transforms
|
|
46
76
|
class GlobalTransform:
|
|
47
77
|
name: str
|
|
@@ -60,8 +90,8 @@ class TransformPreprocParameter(TypedDict):
|
|
|
60
90
|
|
|
61
91
|
|
|
62
92
|
class ChangeSetModelTransform(ChangeSetModelPreproc):
|
|
63
|
-
_before_parameters: Final[dict]
|
|
64
|
-
_after_parameters: Final[dict]
|
|
93
|
+
_before_parameters: Final[dict[str, EngineParameter] | None]
|
|
94
|
+
_after_parameters: Final[dict[str, EngineParameter] | None]
|
|
65
95
|
_before_template: Final[Maybe[dict]]
|
|
66
96
|
_after_template: Final[Maybe[dict]]
|
|
67
97
|
|
|
@@ -79,44 +109,13 @@ class ChangeSetModelTransform(ChangeSetModelPreproc):
|
|
|
79
109
|
self._before_template = before_template or Nothing
|
|
80
110
|
self._after_template = after_template or Nothing
|
|
81
111
|
|
|
82
|
-
def
|
|
83
|
-
self
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
# Enable compatability with v1 util.
|
|
88
|
-
# TODO: port v1's SSM parameter resolution
|
|
89
|
-
|
|
90
|
-
parameter_value_delta = super().visit_node_parameter(node_parameter=node_parameter)
|
|
91
|
-
parameter_value_before = parameter_value_delta.before
|
|
92
|
-
parameter_value_after = parameter_value_delta.after
|
|
93
|
-
|
|
94
|
-
parameter_type_delta = self.visit(node_parameter.type_)
|
|
95
|
-
parameter_type_before = parameter_type_delta.before
|
|
96
|
-
parameter_type_after = parameter_type_delta.after
|
|
97
|
-
|
|
98
|
-
parameter_key = node_parameter.name
|
|
99
|
-
|
|
100
|
-
before = Nothing
|
|
101
|
-
if not is_nothing(parameter_value_before):
|
|
102
|
-
before = TransformPreprocParameter(
|
|
103
|
-
ParameterKey=parameter_key,
|
|
104
|
-
ParameterValue=parameter_value_before,
|
|
105
|
-
ParameterType=parameter_type_before
|
|
106
|
-
if not is_nothing(parameter_type_before)
|
|
107
|
-
else None,
|
|
108
|
-
)
|
|
109
|
-
after = Nothing
|
|
110
|
-
if not is_nothing(parameter_value_after):
|
|
111
|
-
after = TransformPreprocParameter(
|
|
112
|
-
ParameterKey=parameter_key,
|
|
113
|
-
ParameterValue=parameter_value_after,
|
|
114
|
-
ParameterType=parameter_type_after
|
|
115
|
-
if not is_nothing(parameter_type_after)
|
|
116
|
-
else None,
|
|
117
|
-
)
|
|
112
|
+
def transform(self) -> tuple[dict, dict]:
|
|
113
|
+
self._setup_runtime_cache()
|
|
114
|
+
self._execute_local_transforms()
|
|
115
|
+
transformed_before_template, transformed_after_template = self._execute_global_transforms()
|
|
116
|
+
self._save_runtime_cache()
|
|
118
117
|
|
|
119
|
-
return
|
|
118
|
+
return transformed_before_template, transformed_after_template
|
|
120
119
|
|
|
121
120
|
# Ported from v1:
|
|
122
121
|
@staticmethod
|
|
@@ -143,102 +142,92 @@ class ChangeSetModelTransform(ChangeSetModelPreproc):
|
|
|
143
142
|
if region_before is not None:
|
|
144
143
|
os.environ["AWS_DEFAULT_REGION"] = region_before
|
|
145
144
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
global_transform: GlobalTransform, template: dict, parameters: dict, account_id, region_name
|
|
149
|
-
) -> dict:
|
|
150
|
-
location = global_transform.parameters.get("Location")
|
|
145
|
+
def _compute_include_transform(self, parameters: dict, fragment: dict) -> dict:
|
|
146
|
+
location = parameters.get("Location")
|
|
151
147
|
if not location or not location.startswith("s3://"):
|
|
152
148
|
raise FailedTransformationException(
|
|
153
149
|
transformation=INCLUDE_TRANSFORM,
|
|
154
|
-
message="Unexpected Location parameter for AWS::Include transformer:
|
|
150
|
+
message=f"Unexpected Location parameter for AWS::Include transformer: {location}",
|
|
155
151
|
)
|
|
156
152
|
|
|
157
|
-
s3_client = connect_to(
|
|
153
|
+
s3_client = connect_to(
|
|
154
|
+
aws_access_key_id=self._change_set.account_id, region_name=self._change_set.region_name
|
|
155
|
+
).s3
|
|
158
156
|
bucket, _, path = location.removeprefix("s3://").partition("/")
|
|
159
157
|
try:
|
|
160
158
|
content = testutil.download_s3_object(s3_client, bucket, path)
|
|
161
159
|
except ClientError:
|
|
162
160
|
raise FailedTransformationException(
|
|
163
161
|
transformation=INCLUDE_TRANSFORM,
|
|
164
|
-
message="Error downloading S3 object '
|
|
162
|
+
message=f"Error downloading S3 object '{bucket}/{path}'",
|
|
165
163
|
)
|
|
166
164
|
try:
|
|
167
165
|
template_to_include = parse_template(content)
|
|
168
166
|
except Exception as e:
|
|
169
167
|
raise FailedTransformationException(transformation=INCLUDE_TRANSFORM, message=str(e))
|
|
170
|
-
return {**template, **template_to_include}
|
|
171
168
|
|
|
172
|
-
|
|
173
|
-
def _apply_global_macro_transformation(
|
|
174
|
-
account_id: str,
|
|
175
|
-
region_name,
|
|
176
|
-
global_transform: GlobalTransform,
|
|
177
|
-
template: dict,
|
|
178
|
-
parameters: dict,
|
|
179
|
-
) -> dict | None:
|
|
180
|
-
macro_name = global_transform.name
|
|
181
|
-
macros_store = get_cloudformation_store(
|
|
182
|
-
account_id=account_id, region_name=region_name
|
|
183
|
-
).macros
|
|
184
|
-
macro = macros_store.get(macro_name)
|
|
185
|
-
if macro is None:
|
|
186
|
-
raise RuntimeError(f"No definitions for global transform '{macro_name}'")
|
|
187
|
-
transformation_parameters = global_transform.parameters or {}
|
|
188
|
-
transformed_template = execute_macro(
|
|
189
|
-
account_id,
|
|
190
|
-
region_name,
|
|
191
|
-
parsed_template=template,
|
|
192
|
-
macro=macro,
|
|
193
|
-
stack_parameters=parameters,
|
|
194
|
-
transformation_parameters=transformation_parameters,
|
|
195
|
-
)
|
|
196
|
-
# The type annotation on the v1 util appears to be incorrect.
|
|
197
|
-
return transformed_template # noqa
|
|
169
|
+
return {**fragment, **template_to_include}
|
|
198
170
|
|
|
199
171
|
def _apply_global_transform(
|
|
200
|
-
self,
|
|
172
|
+
self,
|
|
173
|
+
global_transform: GlobalTransform,
|
|
174
|
+
template: dict,
|
|
175
|
+
parameters: dict[str, EngineParameter],
|
|
201
176
|
) -> dict:
|
|
202
177
|
transform_name = global_transform.name
|
|
203
178
|
if transform_name == EXTENSIONS_TRANSFORM:
|
|
204
|
-
|
|
205
|
-
|
|
179
|
+
resources = template["Resources"]
|
|
180
|
+
mappings = template.get("Mappings", {})
|
|
181
|
+
conditions = template.get("Conditions", {})
|
|
182
|
+
|
|
183
|
+
resolve_context = ResolveRefsRecursivelyContext(
|
|
184
|
+
self._change_set.account_id,
|
|
185
|
+
self._change_set.region_name,
|
|
186
|
+
self._change_set.stack.stack_name,
|
|
187
|
+
resources,
|
|
188
|
+
mappings,
|
|
189
|
+
conditions,
|
|
190
|
+
parameters=engine_parameters_to_stack_parameters(parameters),
|
|
191
|
+
)
|
|
192
|
+
transformed_template = apply_language_extensions_transform(template, resolve_context)
|
|
206
193
|
elif transform_name == SERVERLESS_TRANSFORM:
|
|
194
|
+
# serverless transform just requires the key/value pairs
|
|
195
|
+
serverless_parameters = {}
|
|
196
|
+
for name, param in parameters.items():
|
|
197
|
+
serverless_parameters[name] = param.get("resolved_value") or engine_parameter_value(
|
|
198
|
+
param
|
|
199
|
+
)
|
|
207
200
|
transformed_template = self._apply_global_serverless_transformation(
|
|
208
201
|
region_name=self._change_set.region_name,
|
|
209
202
|
template=template,
|
|
210
|
-
parameters=
|
|
203
|
+
parameters=serverless_parameters,
|
|
211
204
|
)
|
|
212
205
|
elif transform_name == SECRETSMANAGER_TRANSFORM:
|
|
213
206
|
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-aws-secretsmanager.html
|
|
214
207
|
LOG.warning("%s is not yet supported. Ignoring.", SECRETSMANAGER_TRANSFORM)
|
|
215
208
|
transformed_template = template
|
|
216
209
|
elif transform_name == INCLUDE_TRANSFORM:
|
|
217
|
-
transformed_template = self.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
account_id=self._change_set.account_id,
|
|
221
|
-
template=template,
|
|
222
|
-
parameters=parameters,
|
|
210
|
+
transformed_template = self._compute_include_transform(
|
|
211
|
+
parameters=global_transform.parameters,
|
|
212
|
+
fragment=template,
|
|
223
213
|
)
|
|
224
214
|
else:
|
|
225
|
-
transformed_template = self.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
global_transform
|
|
229
|
-
|
|
230
|
-
|
|
215
|
+
transformed_template = self._invoke_macro(
|
|
216
|
+
name=global_transform.name,
|
|
217
|
+
parameters=global_transform.parameters
|
|
218
|
+
if not is_nothing(global_transform.parameters)
|
|
219
|
+
else {},
|
|
220
|
+
fragment=template,
|
|
221
|
+
allow_string=False,
|
|
231
222
|
)
|
|
232
223
|
return transformed_template
|
|
233
224
|
|
|
234
|
-
def
|
|
235
|
-
self._setup_runtime_cache()
|
|
236
|
-
|
|
225
|
+
def _execute_local_transforms(self):
|
|
237
226
|
node_template = self._change_set.update_model.node_template
|
|
227
|
+
self.visit_node_resources(node_template.resources)
|
|
238
228
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
parameters_after = parameters_delta.after
|
|
229
|
+
def _execute_global_transforms(self) -> tuple[dict, dict]:
|
|
230
|
+
node_template = self._change_set.update_model.node_template
|
|
242
231
|
|
|
243
232
|
transform_delta: PreprocEntityDelta[list[GlobalTransform], list[GlobalTransform]] = (
|
|
244
233
|
self.visit_node_transform(node_template.transform)
|
|
@@ -248,33 +237,36 @@ class ChangeSetModelTransform(ChangeSetModelPreproc):
|
|
|
248
237
|
|
|
249
238
|
transformed_before_template = self._before_template
|
|
250
239
|
if transform_before and not is_nothing(self._before_template):
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
240
|
+
if _SCOPE_TRANSFORM_TEMPLATE_OUTCOME in self._before_cache:
|
|
241
|
+
transformed_before_template = self._before_cache[_SCOPE_TRANSFORM_TEMPLATE_OUTCOME]
|
|
242
|
+
else:
|
|
254
243
|
for before_global_transform in transform_before:
|
|
255
244
|
if not is_nothing(before_global_transform.name):
|
|
256
245
|
transformed_before_template = self._apply_global_transform(
|
|
257
246
|
global_transform=before_global_transform,
|
|
258
|
-
parameters=
|
|
247
|
+
parameters=self._before_parameters,
|
|
259
248
|
template=transformed_before_template,
|
|
260
249
|
)
|
|
250
|
+
|
|
251
|
+
# Macro transformations won't remove the transform from the template
|
|
252
|
+
if "Transform" in transformed_before_template:
|
|
253
|
+
transformed_before_template.pop("Transform")
|
|
261
254
|
self._before_cache[_SCOPE_TRANSFORM_TEMPLATE_OUTCOME] = transformed_before_template
|
|
262
255
|
|
|
263
256
|
transformed_after_template = self._after_template
|
|
264
257
|
if transform_after and not is_nothing(self._after_template):
|
|
265
|
-
transformed_after_template = self.
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
self._save_runtime_cache()
|
|
258
|
+
transformed_after_template = self._after_template
|
|
259
|
+
for after_global_transform in transform_after:
|
|
260
|
+
if not is_nothing(after_global_transform.name):
|
|
261
|
+
transformed_after_template = self._apply_global_transform(
|
|
262
|
+
global_transform=after_global_transform,
|
|
263
|
+
parameters=self._after_parameters,
|
|
264
|
+
template=transformed_after_template,
|
|
265
|
+
)
|
|
266
|
+
# Macro transformations won't remove the transform from the template
|
|
267
|
+
if "Transform" in transformed_after_template:
|
|
268
|
+
transformed_after_template.pop("Transform")
|
|
269
|
+
self._after_cache[_SCOPE_TRANSFORM_TEMPLATE_OUTCOME] = transformed_after_template
|
|
278
270
|
|
|
279
271
|
return transformed_before_template, transformed_after_template
|
|
280
272
|
|
|
@@ -301,6 +293,9 @@ class ChangeSetModelTransform(ChangeSetModelPreproc):
|
|
|
301
293
|
before = [] if change_type != ChangeType.CREATED else Nothing
|
|
302
294
|
after = [] if change_type != ChangeType.REMOVED else Nothing
|
|
303
295
|
for change_set_entity in node_transform.global_transforms:
|
|
296
|
+
if not isinstance(change_set_entity.name.value, str):
|
|
297
|
+
raise ValidationError("Key Name of transform definition must be a string.")
|
|
298
|
+
|
|
304
299
|
delta: PreprocEntityDelta[GlobalTransform, GlobalTransform] = self.visit(
|
|
305
300
|
change_set_entity=change_set_entity
|
|
306
301
|
)
|
|
@@ -311,3 +306,242 @@ class ChangeSetModelTransform(ChangeSetModelPreproc):
|
|
|
311
306
|
if not is_nothing(after) and not is_nothing(delta_after):
|
|
312
307
|
after.append(delta_after)
|
|
313
308
|
return PreprocEntityDelta(before=before, after=after)
|
|
309
|
+
|
|
310
|
+
def _compute_fn_transform(
|
|
311
|
+
self, macro_definition: Any, siblings: Any, allow_string: False
|
|
312
|
+
) -> Any:
|
|
313
|
+
def _normalize_transform(obj):
|
|
314
|
+
transforms = []
|
|
315
|
+
|
|
316
|
+
if isinstance(obj, str):
|
|
317
|
+
transforms.append({"Name": obj, "Parameters": {}})
|
|
318
|
+
|
|
319
|
+
if isinstance(obj, dict):
|
|
320
|
+
transforms.append(obj)
|
|
321
|
+
|
|
322
|
+
if isinstance(obj, list):
|
|
323
|
+
for v in obj:
|
|
324
|
+
if isinstance(v, str):
|
|
325
|
+
transforms.append({"Name": v, "Parameters": {}})
|
|
326
|
+
|
|
327
|
+
if isinstance(v, dict):
|
|
328
|
+
if not v.get("Parameters"):
|
|
329
|
+
v["Parameters"] = {}
|
|
330
|
+
transforms.append(v)
|
|
331
|
+
|
|
332
|
+
return transforms
|
|
333
|
+
|
|
334
|
+
normalized_transforms = _normalize_transform(macro_definition)
|
|
335
|
+
transform_output = copy.deepcopy(siblings)
|
|
336
|
+
for transform in normalized_transforms:
|
|
337
|
+
transform_name = transform["Name"]
|
|
338
|
+
if transform_name == INCLUDE_TRANSFORM:
|
|
339
|
+
transform_output = self._compute_include_transform(
|
|
340
|
+
parameters=transform["Parameters"], fragment=transform_output
|
|
341
|
+
)
|
|
342
|
+
else:
|
|
343
|
+
transform_output: dict | str = self._invoke_macro(
|
|
344
|
+
fragment=transform_output,
|
|
345
|
+
name=transform["Name"],
|
|
346
|
+
parameters=transform.get("Parameters", {}),
|
|
347
|
+
allow_string=allow_string,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
if isinstance(transform_output, dict) and FnTransform in transform_output:
|
|
351
|
+
transform_output.pop(FnTransform)
|
|
352
|
+
|
|
353
|
+
return transform_output
|
|
354
|
+
|
|
355
|
+
def _replace_at_jsonpath(self, template: dict, path: str, result: Any):
|
|
356
|
+
pattern = jsonpath_ng.parse(path)
|
|
357
|
+
result_template = pattern.update(template, result)
|
|
358
|
+
|
|
359
|
+
return result_template
|
|
360
|
+
|
|
361
|
+
def visit_node_for_each(self, node_foreach: NodeForEach) -> PreprocEntityDelta:
|
|
362
|
+
return PreprocEntityDelta()
|
|
363
|
+
|
|
364
|
+
def visit_node_intrinsic_function_fn_transform(
|
|
365
|
+
self, node_intrinsic_function: NodeIntrinsicFunctionFnTransform
|
|
366
|
+
) -> PreprocEntityDelta:
|
|
367
|
+
arguments_delta = self.visit(node_intrinsic_function.arguments)
|
|
368
|
+
parent_json_path = node_intrinsic_function.scope.parent.jsonpath
|
|
369
|
+
|
|
370
|
+
# Only when a FnTransform is used as Property value the macro function is allowed to return a str
|
|
371
|
+
property_value_regex = r"\.(Properties)"
|
|
372
|
+
allow_string = False
|
|
373
|
+
if re.search(property_value_regex, parent_json_path):
|
|
374
|
+
allow_string = True
|
|
375
|
+
|
|
376
|
+
if not is_nothing(arguments_delta.before):
|
|
377
|
+
before = self._compute_fn_transform(
|
|
378
|
+
arguments_delta.before,
|
|
379
|
+
node_intrinsic_function.before_siblings,
|
|
380
|
+
allow_string=allow_string,
|
|
381
|
+
)
|
|
382
|
+
updated_before_template = self._replace_at_jsonpath(
|
|
383
|
+
self._before_template, parent_json_path, before
|
|
384
|
+
)
|
|
385
|
+
self._after_cache[_SCOPE_TRANSFORM_TEMPLATE_OUTCOME] = updated_before_template
|
|
386
|
+
else:
|
|
387
|
+
before = Nothing
|
|
388
|
+
|
|
389
|
+
if not is_nothing(arguments_delta.after):
|
|
390
|
+
after = self._compute_fn_transform(
|
|
391
|
+
arguments_delta.after,
|
|
392
|
+
node_intrinsic_function.after_siblings,
|
|
393
|
+
allow_string=allow_string,
|
|
394
|
+
)
|
|
395
|
+
updated_after_template = self._replace_at_jsonpath(
|
|
396
|
+
self._after_template, parent_json_path, after
|
|
397
|
+
)
|
|
398
|
+
self._after_cache[_SCOPE_TRANSFORM_TEMPLATE_OUTCOME] = updated_after_template
|
|
399
|
+
else:
|
|
400
|
+
after = Nothing
|
|
401
|
+
|
|
402
|
+
self._save_runtime_cache()
|
|
403
|
+
return PreprocEntityDelta(before=before, after=after)
|
|
404
|
+
|
|
405
|
+
def visit_node_properties(
|
|
406
|
+
self, node_properties: NodeProperties
|
|
407
|
+
) -> PreprocEntityDelta[PreprocProperties, PreprocProperties]:
|
|
408
|
+
if not is_nothing(node_properties.fn_transform):
|
|
409
|
+
self.visit_node_intrinsic_function_fn_transform(node_properties.fn_transform)
|
|
410
|
+
|
|
411
|
+
return super().visit_node_properties(node_properties=node_properties)
|
|
412
|
+
|
|
413
|
+
def visit_node_resource(self, node_resource: NodeResource) -> PreprocEntityDelta:
|
|
414
|
+
if not is_nothing(node_resource.fn_transform):
|
|
415
|
+
self.visit_node_intrinsic_function_fn_transform(
|
|
416
|
+
node_intrinsic_function=node_resource.fn_transform
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
try:
|
|
420
|
+
if delta := super().visit_node_resource(node_resource):
|
|
421
|
+
return delta
|
|
422
|
+
return super().visit_node_properties(node_resource.properties)
|
|
423
|
+
except RuntimeError:
|
|
424
|
+
return super().visit_node_properties(node_resource.properties)
|
|
425
|
+
|
|
426
|
+
def visit_node_resources(self, node_resources: NodeResources) -> PreprocEntityDelta:
|
|
427
|
+
if not is_nothing(node_resources.fn_transform):
|
|
428
|
+
self.visit_node_intrinsic_function_fn_transform(
|
|
429
|
+
node_intrinsic_function=node_resources.fn_transform
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
return super().visit_node_resources(node_resources=node_resources)
|
|
433
|
+
|
|
434
|
+
def _invoke_macro(self, name: str, parameters: dict, fragment: dict, allow_string=False):
|
|
435
|
+
account_id = self._change_set.account_id
|
|
436
|
+
region_name = self._change_set.region_name
|
|
437
|
+
macro_definition = get_cloudformation_store(
|
|
438
|
+
account_id=account_id, region_name=region_name
|
|
439
|
+
).macros.get(name)
|
|
440
|
+
|
|
441
|
+
if not macro_definition:
|
|
442
|
+
raise FailedTransformationException(name, f"Transformation {name} is not supported.")
|
|
443
|
+
|
|
444
|
+
simplified_parameters = {}
|
|
445
|
+
if resolved_parameters := self._change_set.resolved_parameters:
|
|
446
|
+
for key, resolved_parameter in resolved_parameters.items():
|
|
447
|
+
final_value = engine_parameter_value(resolved_parameter)
|
|
448
|
+
simplified_parameters[key] = (
|
|
449
|
+
final_value.split(",")
|
|
450
|
+
if resolved_parameter["type_"] == "CommaDelimitedList"
|
|
451
|
+
else final_value
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
transformation_id = f"{account_id}::{name}"
|
|
455
|
+
event = {
|
|
456
|
+
"region": region_name,
|
|
457
|
+
"accountId": account_id,
|
|
458
|
+
"fragment": fragment,
|
|
459
|
+
"transformId": transformation_id,
|
|
460
|
+
"params": parameters,
|
|
461
|
+
"requestId": long_uid(),
|
|
462
|
+
"templateParameterValues": simplified_parameters,
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
client = connect_to(aws_access_key_id=account_id, region_name=region_name).lambda_
|
|
466
|
+
try:
|
|
467
|
+
invocation = client.invoke(
|
|
468
|
+
FunctionName=macro_definition["FunctionName"], Payload=json.dumps(event)
|
|
469
|
+
)
|
|
470
|
+
except ClientError:
|
|
471
|
+
LOG.error(
|
|
472
|
+
"client error executing lambda function '%s' with payload '%s'",
|
|
473
|
+
macro_definition["FunctionName"],
|
|
474
|
+
json.dumps(event),
|
|
475
|
+
)
|
|
476
|
+
raise
|
|
477
|
+
if invocation.get("StatusCode") != 200 or invocation.get("FunctionError") == "Unhandled":
|
|
478
|
+
raise FailedTransformationException(
|
|
479
|
+
transformation=name,
|
|
480
|
+
message=f"Received malformed response from transform {transformation_id}. Rollback requested by user.",
|
|
481
|
+
)
|
|
482
|
+
result = json.loads(invocation["Payload"].read())
|
|
483
|
+
|
|
484
|
+
if result.get("status") != "success":
|
|
485
|
+
error_message = result.get("errorMessage")
|
|
486
|
+
message = (
|
|
487
|
+
f"Transform {transformation_id} failed with: {error_message}. Rollback requested by user."
|
|
488
|
+
if error_message
|
|
489
|
+
else f"Transform {transformation_id} failed without an error message.. Rollback requested by user."
|
|
490
|
+
)
|
|
491
|
+
raise FailedTransformationException(transformation=name, message=message)
|
|
492
|
+
|
|
493
|
+
if not isinstance(result.get("fragment"), dict) and not allow_string:
|
|
494
|
+
raise FailedTransformationException(
|
|
495
|
+
transformation=name,
|
|
496
|
+
message="Template format error: unsupported structure.. Rollback requested by user.",
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
return result.get("fragment")
|
|
500
|
+
|
|
501
|
+
def visit_node_intrinsic_function_fn_get_att(
|
|
502
|
+
self, node_intrinsic_function: NodeIntrinsicFunction
|
|
503
|
+
) -> PreprocEntityDelta:
|
|
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)
|
|
508
|
+
|
|
509
|
+
def visit_node_intrinsic_function_fn_sub(
|
|
510
|
+
self, node_intrinsic_function: NodeIntrinsicFunction
|
|
511
|
+
) -> PreprocEntityDelta:
|
|
512
|
+
try:
|
|
513
|
+
# If an argument is a Parameter it should be resolved, any other case, ignore it
|
|
514
|
+
return super().visit_node_intrinsic_function_fn_sub(node_intrinsic_function)
|
|
515
|
+
except RuntimeError:
|
|
516
|
+
return self.visit(node_intrinsic_function.arguments)
|
|
517
|
+
|
|
518
|
+
def visit_node_intrinsic_function_fn_split(
|
|
519
|
+
self, node_intrinsic_function: NodeIntrinsicFunction
|
|
520
|
+
) -> PreprocEntityDelta:
|
|
521
|
+
try:
|
|
522
|
+
# If an argument is a Parameter it should be resolved, any other case, ignore it
|
|
523
|
+
return super().visit_node_intrinsic_function_fn_split(node_intrinsic_function)
|
|
524
|
+
except RuntimeError:
|
|
525
|
+
return self.visit(node_intrinsic_function.arguments)
|
|
526
|
+
|
|
527
|
+
def visit_node_intrinsic_function_fn_select(
|
|
528
|
+
self, node_intrinsic_function: NodeIntrinsicFunction
|
|
529
|
+
) -> PreprocEntityDelta:
|
|
530
|
+
try:
|
|
531
|
+
# If an argument is a Parameter it should be resolved, any other case, ignore it
|
|
532
|
+
return super().visit_node_intrinsic_function_fn_select(node_intrinsic_function)
|
|
533
|
+
except RuntimeError:
|
|
534
|
+
return self.visit(node_intrinsic_function.arguments)
|
|
535
|
+
|
|
536
|
+
def visit_node_property(self, node_property: NodeProperty) -> PreprocEntityDelta:
|
|
537
|
+
try:
|
|
538
|
+
return super().visit_node_property(node_property)
|
|
539
|
+
except ParamValidationError:
|
|
540
|
+
return self.visit(node_property.value)
|
|
541
|
+
|
|
542
|
+
# ignore errors from dynamic replacements
|
|
543
|
+
def _maybe_perform_dynamic_replacements(self, delta: PreprocEntityDelta) -> PreprocEntityDelta:
|
|
544
|
+
try:
|
|
545
|
+
return super()._maybe_perform_dynamic_replacements(delta)
|
|
546
|
+
except Exception:
|
|
547
|
+
return delta
|