localstack-core 4.10.1.dev42__py3-none-any.whl → 4.12.1.dev18__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.
Potentially problematic release.
This version of localstack-core might be problematic. Click here for more details.
- localstack/aws/api/apigateway/__init__.py +42 -0
- localstack/aws/api/cloudformation/__init__.py +161 -0
- localstack/aws/api/ec2/__init__.py +1178 -12
- localstack/aws/api/iam/__init__.py +228 -0
- localstack/aws/api/kms/__init__.py +1 -0
- localstack/aws/api/lambda_/__init__.py +1034 -66
- localstack/aws/api/logs/__init__.py +500 -0
- localstack/aws/api/opensearch/__init__.py +100 -0
- localstack/aws/api/redshift/__init__.py +69 -0
- localstack/aws/api/resourcegroupstaggingapi/__init__.py +36 -0
- localstack/aws/api/route53/__init__.py +45 -0
- localstack/aws/api/route53resolver/__init__.py +1 -0
- localstack/aws/api/s3/__init__.py +64 -0
- localstack/aws/api/s3control/__init__.py +19 -0
- localstack/aws/api/secretsmanager/__init__.py +37 -23
- localstack/aws/api/stepfunctions/__init__.py +52 -10
- localstack/aws/api/sts/__init__.py +52 -0
- localstack/aws/connect.py +35 -15
- localstack/aws/handlers/logging.py +8 -4
- localstack/aws/handlers/service.py +11 -2
- localstack/aws/protocol/serializer.py +1 -1
- localstack/config.py +8 -0
- localstack/constants.py +3 -0
- localstack/deprecations.py +0 -6
- localstack/dev/kubernetes/__main__.py +39 -14
- localstack/runtime/analytics.py +11 -0
- localstack/services/acm/provider.py +17 -1
- localstack/services/apigateway/legacy/provider.py +28 -15
- localstack/services/cloudformation/engine/template_preparer.py +6 -2
- localstack/services/cloudformation/engine/v2/change_set_model.py +9 -0
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +15 -1
- localstack/services/cloudformation/engine/v2/change_set_resource_support_checker.py +114 -0
- localstack/services/cloudformation/provider.py +26 -1
- localstack/services/cloudformation/provider_utils.py +20 -0
- localstack/services/cloudformation/resource_provider.py +5 -4
- localstack/services/cloudformation/scaffolding/__main__.py +94 -22
- localstack/services/cloudformation/v2/provider.py +41 -0
- localstack/services/cloudwatch/provider.py +10 -3
- localstack/services/cloudwatch/provider_v2.py +6 -3
- localstack/services/configservice/provider.py +5 -1
- localstack/services/dynamodb/provider.py +1 -0
- localstack/services/dynamodb/v2/provider.py +1 -0
- localstack/services/dynamodbstreams/provider.py +6 -0
- localstack/services/dynamodbstreams/v2/provider.py +6 -0
- localstack/services/ec2/provider.py +6 -0
- localstack/services/es/provider.py +6 -0
- localstack/services/events/provider.py +4 -0
- localstack/services/events/v1/provider.py +9 -0
- localstack/services/firehose/provider.py +5 -0
- localstack/services/iam/provider.py +4 -0
- localstack/services/kinesis/packages.py +1 -1
- localstack/services/kms/models.py +16 -22
- localstack/services/kms/provider.py +4 -0
- localstack/services/lambda_/analytics.py +11 -2
- localstack/services/lambda_/api_utils.py +37 -20
- localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +1 -1
- localstack/services/lambda_/invocation/assignment.py +4 -1
- localstack/services/lambda_/invocation/event_manager.py +15 -11
- localstack/services/lambda_/invocation/execution_environment.py +21 -2
- localstack/services/lambda_/invocation/lambda_models.py +31 -2
- localstack/services/lambda_/invocation/lambda_service.py +62 -3
- localstack/services/lambda_/invocation/models.py +9 -1
- localstack/services/lambda_/invocation/version_manager.py +18 -3
- localstack/services/lambda_/provider.py +307 -106
- localstack/services/lambda_/resource_providers/aws_lambda_function.py +33 -1
- localstack/services/lambda_/runtimes.py +3 -1
- localstack/services/logs/provider.py +9 -0
- localstack/services/opensearch/packages.py +34 -20
- localstack/services/opensearch/provider.py +53 -3
- localstack/services/resource_groups/provider.py +5 -1
- localstack/services/resourcegroupstaggingapi/provider.py +6 -1
- localstack/services/route53/provider.py +7 -0
- localstack/services/route53resolver/provider.py +5 -0
- localstack/services/s3/constants.py +5 -0
- localstack/services/s3/exceptions.py +9 -0
- localstack/services/s3/models.py +9 -1
- localstack/services/s3/provider.py +51 -43
- localstack/services/s3/utils.py +81 -15
- localstack/services/s3control/provider.py +107 -2
- localstack/services/s3control/validation.py +50 -0
- localstack/services/scheduler/provider.py +4 -2
- localstack/services/secretsmanager/provider.py +4 -0
- localstack/services/ses/provider.py +4 -0
- localstack/services/sns/constants.py +16 -1
- localstack/services/sns/provider.py +5 -0
- localstack/services/sns/publisher.py +15 -6
- localstack/services/sns/v2/models.py +9 -0
- localstack/services/sns/v2/provider.py +750 -19
- localstack/services/sns/v2/utils.py +12 -0
- localstack/services/sqs/constants.py +6 -0
- localstack/services/sqs/provider.py +9 -1
- localstack/services/sqs/resource_providers/aws_sqs_queue.py +61 -46
- localstack/services/ssm/provider.py +6 -0
- localstack/services/stepfunctions/asl/component/common/path/result_path.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py +0 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +0 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py +8 -8
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/{mock_eval_utils.py → local_mock_eval_utils.py} +13 -9
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py +6 -6
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +4 -0
- localstack/services/stepfunctions/asl/component/test_state/state/base_mock.py +118 -0
- localstack/services/stepfunctions/asl/component/test_state/state/common.py +82 -0
- localstack/services/stepfunctions/asl/component/test_state/state/execution.py +139 -0
- localstack/services/stepfunctions/asl/component/test_state/state/map.py +77 -0
- localstack/services/stepfunctions/asl/component/test_state/state/task.py +44 -0
- localstack/services/stepfunctions/asl/eval/environment.py +30 -22
- localstack/services/stepfunctions/asl/eval/states.py +1 -1
- localstack/services/stepfunctions/asl/eval/test_state/environment.py +49 -9
- localstack/services/stepfunctions/asl/eval/test_state/program_state.py +22 -0
- localstack/services/stepfunctions/asl/jsonata/jsonata.py +5 -1
- localstack/services/stepfunctions/asl/parse/preprocessor.py +67 -24
- localstack/services/stepfunctions/asl/parse/test_state/asl_parser.py +5 -4
- localstack/services/stepfunctions/asl/parse/test_state/preprocessor.py +222 -31
- localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py +256 -22
- localstack/services/stepfunctions/backend/execution.py +10 -11
- localstack/services/stepfunctions/backend/execution_worker.py +5 -5
- localstack/services/stepfunctions/backend/test_state/execution.py +36 -0
- localstack/services/stepfunctions/backend/test_state/execution_worker.py +33 -1
- localstack/services/stepfunctions/backend/test_state/test_state_mock.py +127 -0
- localstack/services/stepfunctions/local_mocking/__init__.py +9 -0
- localstack/services/stepfunctions/{mocking → local_mocking}/mock_config.py +24 -17
- localstack/services/stepfunctions/provider.py +83 -25
- localstack/services/stepfunctions/test_state/mock_config.py +47 -0
- localstack/services/sts/provider.py +7 -0
- localstack/services/support/provider.py +5 -1
- localstack/services/swf/provider.py +5 -1
- localstack/services/transcribe/provider.py +7 -0
- localstack/testing/aws/lambda_utils.py +1 -1
- localstack/testing/aws/util.py +2 -1
- localstack/testing/config.py +1 -0
- localstack/testing/pytest/fixtures.py +28 -0
- localstack/testing/snapshots/transformer_utility.py +5 -0
- localstack/utils/analytics/publisher.py +37 -155
- localstack/utils/analytics/service_request_aggregator.py +6 -4
- localstack/utils/aws/arns.py +7 -0
- localstack/utils/aws/client_types.py +2 -4
- localstack/utils/batching.py +258 -0
- localstack/utils/bootstrap.py +2 -2
- localstack/utils/catalog/catalog.py +3 -2
- localstack/utils/collections.py +23 -11
- localstack/utils/container_utils/container_client.py +22 -13
- localstack/utils/container_utils/docker_cmd_client.py +6 -6
- localstack/version.py +2 -2
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/METADATA +7 -7
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/RECORD +155 -146
- localstack_core-4.12.1.dev18.dist-info/plux.json +1 -0
- localstack/services/stepfunctions/mocking/__init__.py +0 -0
- localstack/utils/batch_policy.py +0 -124
- localstack_core-4.10.1.dev42.dist-info/plux.json +0 -1
- /localstack/services/stepfunctions/{mocking → local_mocking}/mock_config_file.py +0 -0
- {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack +0 -0
- {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/WHEEL +0 -0
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/entry_points.txt +0 -0
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/top_level.txt +0 -0
|
@@ -6,6 +6,7 @@ from botocore.utils import InvalidArnException
|
|
|
6
6
|
|
|
7
7
|
from localstack.aws.api.sns import InvalidParameterException
|
|
8
8
|
from localstack.services.sns.constants import E164_REGEX, VALID_SUBSCRIPTION_ATTR_NAME
|
|
9
|
+
from localstack.services.sns.v2.models import SnsStore, SnsSubscription
|
|
9
10
|
from localstack.utils.aws.arns import ArnData, parse_arn
|
|
10
11
|
from localstack.utils.strings import short_uid, to_bytes, to_str
|
|
11
12
|
|
|
@@ -136,3 +137,14 @@ def get_region_from_subscription_token(token: str) -> str:
|
|
|
136
137
|
return bytes.fromhex(region).decode("utf-8")
|
|
137
138
|
except (IndexError, ValueError, TypeError, UnicodeDecodeError):
|
|
138
139
|
raise InvalidParameterException("Invalid parameter: Token")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def get_topic_subscriptions(store: SnsStore, topic_arn: str) -> list[SnsSubscription]:
|
|
143
|
+
# TODO: delete this once the legacy v1 implementation has been removed
|
|
144
|
+
if hasattr(store, "topic_subscriptions"):
|
|
145
|
+
sub_arns = store.topic_subscriptions.get(topic_arn, [])
|
|
146
|
+
else:
|
|
147
|
+
sub_arns: list[str] = store.topics[topic_arn].get("subscriptions", [])
|
|
148
|
+
|
|
149
|
+
subscriptions = [store.subscriptions[k] for k in sub_arns if k in store.subscriptions]
|
|
150
|
+
return subscriptions
|
|
@@ -31,6 +31,12 @@ INTERNAL_QUEUE_ATTRIBUTES = [
|
|
|
31
31
|
QueueAttributeName.QueueArn,
|
|
32
32
|
]
|
|
33
33
|
|
|
34
|
+
#
|
|
35
|
+
# If these attributes are set to their default values, they are effectively
|
|
36
|
+
# deleted from the queue attributes and not returned in future calls to get_queue_attributes()
|
|
37
|
+
#
|
|
38
|
+
DELETE_IF_DEFAULT = {"KmsMasterKeyId": "", "KmsDataKeyReusePeriodSeconds": "300"}
|
|
39
|
+
|
|
34
40
|
INVALID_STANDARD_QUEUE_ATTRIBUTES = [
|
|
35
41
|
QueueAttributeName.FifoQueue,
|
|
36
42
|
QueueAttributeName.ContentBasedDeduplication,
|
|
@@ -101,6 +101,7 @@ from localstack.services.sqs.utils import (
|
|
|
101
101
|
parse_queue_url,
|
|
102
102
|
)
|
|
103
103
|
from localstack.services.stores import AccountRegionBundle
|
|
104
|
+
from localstack.state import StateVisitor
|
|
104
105
|
from localstack.utils.aws.arns import parse_arn
|
|
105
106
|
from localstack.utils.bootstrap import is_api_enabled
|
|
106
107
|
from localstack.utils.cloudwatch.cloudwatch_util import (
|
|
@@ -659,6 +660,9 @@ class SqsProvider(SqsApi, ServiceLifecycleHook):
|
|
|
659
660
|
self._router_rules = []
|
|
660
661
|
self._init_cloudwatch_metrics_reporting()
|
|
661
662
|
|
|
663
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
664
|
+
visitor.visit(sqs_stores)
|
|
665
|
+
|
|
662
666
|
@staticmethod
|
|
663
667
|
def get_store(account_id: str, region: str) -> SqsStore:
|
|
664
668
|
return sqs_stores[account_id][region]
|
|
@@ -1269,7 +1273,11 @@ class SqsProvider(SqsApi, ServiceLifecycleHook):
|
|
|
1269
1273
|
for k, v in attributes.items():
|
|
1270
1274
|
if k in sqs_constants.INTERNAL_QUEUE_ATTRIBUTES:
|
|
1271
1275
|
raise InvalidAttributeName(f"Unknown Attribute {k}.")
|
|
1272
|
-
|
|
1276
|
+
if k in sqs_constants.DELETE_IF_DEFAULT and v == sqs_constants.DELETE_IF_DEFAULT[k]:
|
|
1277
|
+
if k in queue.attributes:
|
|
1278
|
+
del queue.attributes[k]
|
|
1279
|
+
else:
|
|
1280
|
+
queue.attributes[k] = v
|
|
1273
1281
|
|
|
1274
1282
|
# Special cases
|
|
1275
1283
|
if queue.attributes.get(QueueAttributeName.Policy) == "":
|
|
@@ -64,6 +64,35 @@ class SQSQueueProvider(ResourceProvider[SQSQueueProperties]):
|
|
|
64
64
|
TYPE = "AWS::SQS::Queue" # Autogenerated. Don't change
|
|
65
65
|
SCHEMA = util.get_schema_path(Path(__file__)) # Autogenerated. Don't change
|
|
66
66
|
|
|
67
|
+
# Values used when a property is removed from a template and needs to be set to its default.
|
|
68
|
+
# If AWS changes their defaults in the future, our parity tests should break.
|
|
69
|
+
DEFAULT_ATTRIBUTE_VALUES = {
|
|
70
|
+
"ReceiveMessageWaitTimeSeconds": "0",
|
|
71
|
+
"DelaySeconds": "0",
|
|
72
|
+
"KmsMasterKeyId": "",
|
|
73
|
+
"RedrivePolicy": "",
|
|
74
|
+
"MessageRetentionPeriod": "345600",
|
|
75
|
+
"MaximumMessageSize": "262144", # Note: CloudFormation sets this to 256KB on update, but 1MB on create
|
|
76
|
+
"VisibilityTimeout": "30",
|
|
77
|
+
"KmsDataKeyReusePeriodSeconds": "300",
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Private method for creating a unique queue name, if none is specified.
|
|
81
|
+
def _autogenerated_queue_name(self, request: ResourceRequest[SQSQueueProperties]) -> str:
|
|
82
|
+
queue_name = util.generate_default_name(request.stack_name, request.logical_resource_id)
|
|
83
|
+
isFifoQueue = request.desired_state.get("FifoQueue")
|
|
84
|
+
|
|
85
|
+
# Note that it's an SQS FIFO queue only if the FifoQueue property is set to boolean True, or the string "true"
|
|
86
|
+
# (case insensitive). If it's None (property was omitted) or False, or any type of string (e.g. a typo
|
|
87
|
+
# such as "Fasle"), then it's not a FIFO queue. This extra check is needed because the CloudFormation engine
|
|
88
|
+
# doesn't fully validate the FifoQueue property before passing it to the resource provider.
|
|
89
|
+
if (
|
|
90
|
+
isFifoQueue == True # noqa: E712
|
|
91
|
+
or (isinstance(isFifoQueue, str) and isFifoQueue.lower() == "true")
|
|
92
|
+
):
|
|
93
|
+
queue_name = f"{queue_name[:-5]}.fifo"
|
|
94
|
+
return queue_name
|
|
95
|
+
|
|
67
96
|
def create(
|
|
68
97
|
self,
|
|
69
98
|
request: ResourceRequest[SQSQueueProperties],
|
|
@@ -74,8 +103,6 @@ class SQSQueueProvider(ResourceProvider[SQSQueueProperties]):
|
|
|
74
103
|
Primary identifier fields:
|
|
75
104
|
- /properties/QueueUrl
|
|
76
105
|
|
|
77
|
-
|
|
78
|
-
|
|
79
106
|
Create-only properties:
|
|
80
107
|
- /properties/FifoQueue
|
|
81
108
|
- /properties/QueueName
|
|
@@ -92,26 +119,13 @@ class SQSQueueProvider(ResourceProvider[SQSQueueProperties]):
|
|
|
92
119
|
- sqs:TagQueue
|
|
93
120
|
|
|
94
121
|
"""
|
|
95
|
-
# TODO: validations
|
|
122
|
+
# TODO: validations - what validations are needed?
|
|
96
123
|
model = request.desired_state
|
|
97
124
|
sqs = request.aws_client_factory.sqs
|
|
98
125
|
|
|
99
|
-
if
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
queue_name = model.get("QueueName")
|
|
103
|
-
if not queue_name:
|
|
104
|
-
# TODO: verify patterns here
|
|
105
|
-
if model.get("FifoQueue"):
|
|
106
|
-
queue_name = util.generate_default_name(
|
|
107
|
-
request.stack_name, request.logical_resource_id
|
|
108
|
-
)[:-5]
|
|
109
|
-
queue_name = f"{queue_name}.fifo"
|
|
110
|
-
else:
|
|
111
|
-
queue_name = util.generate_default_name(
|
|
112
|
-
request.stack_name, request.logical_resource_id
|
|
113
|
-
)
|
|
114
|
-
model["QueueName"] = queue_name
|
|
126
|
+
# if no QueueName is specified, automatically generate one
|
|
127
|
+
if not model.get("QueueName"):
|
|
128
|
+
model["QueueName"] = self._autogenerated_queue_name(request)
|
|
115
129
|
|
|
116
130
|
attributes = self._compile_sqs_queue_attributes(model)
|
|
117
131
|
result = request.aws_client_factory.sqs.create_queue(
|
|
@@ -184,38 +198,30 @@ class SQSQueueProvider(ResourceProvider[SQSQueueProperties]):
|
|
|
184
198
|
"""
|
|
185
199
|
sqs = request.aws_client_factory.sqs
|
|
186
200
|
model = request.desired_state
|
|
201
|
+
prev_model = request.previous_state
|
|
187
202
|
|
|
188
203
|
assert request.previous_state is not None
|
|
189
204
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
request.desired_state.get("FifoQueue", request.previous_state.get("FifoQueue"))
|
|
195
|
-
!= request.previous_state.get("FifoQueue")
|
|
205
|
+
queue_url = prev_model["QueueUrl"]
|
|
206
|
+
self._populate_missing_attributes_with_defaults(model)
|
|
207
|
+
sqs.set_queue_attributes(
|
|
208
|
+
QueueUrl=queue_url, Attributes=self._compile_sqs_queue_attributes(model)
|
|
196
209
|
)
|
|
197
210
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
sqs.delete_queue(QueueUrl=request.previous_state["QueueUrl"])
|
|
213
|
-
# create new queue (TODO: re-use create logic to make this more robust, e.g. for
|
|
214
|
-
# auto-generated queue names)
|
|
215
|
-
model["QueueUrl"] = sqs.create_queue(QueueName=queue_name)["QueueUrl"]
|
|
216
|
-
model["Arn"] = sqs.get_queue_attributes(
|
|
217
|
-
QueueUrl=model["QueueUrl"], AttributeNames=["QueueArn"]
|
|
218
|
-
)["Attributes"]["QueueArn"]
|
|
211
|
+
(tags_to_remove, tags_to_add_or_update) = util.resource_tags_to_remove_or_update(
|
|
212
|
+
prev_model.get("Tags", []), model.get("Tags", [])
|
|
213
|
+
)
|
|
214
|
+
sqs.untag_queue(QueueUrl=queue_url, TagKeys=tags_to_remove)
|
|
215
|
+
sqs.tag_queue(QueueUrl=queue_url, Tags=tags_to_add_or_update)
|
|
216
|
+
|
|
217
|
+
model["QueueUrl"] = queue_url
|
|
218
|
+
model["Arn"] = request.previous_state["Arn"]
|
|
219
|
+
|
|
220
|
+
# For QueueName and FifoQueue, always use the value from the previous model. These fields
|
|
221
|
+
# are create-only, so they cannot be changed via an update (even though they might be omitted)
|
|
222
|
+
model["QueueName"] = prev_model.get("QueueName")
|
|
223
|
+
model["FifoQueue"] = prev_model.get("FifoQueue", False)
|
|
224
|
+
|
|
219
225
|
return ProgressEvent(OperationStatus.SUCCESS, resource_model=model)
|
|
220
226
|
|
|
221
227
|
def _compile_sqs_queue_attributes(self, properties: SQSQueueProperties) -> dict[str, str]:
|
|
@@ -250,6 +256,15 @@ class SQSQueueProvider(ResourceProvider[SQSQueueProperties]):
|
|
|
250
256
|
|
|
251
257
|
return result
|
|
252
258
|
|
|
259
|
+
def _populate_missing_attributes_with_defaults(self, properties: SQSQueueProperties) -> None:
|
|
260
|
+
"""
|
|
261
|
+
For any attribute that is missing from the desired state, populate it with the default value.
|
|
262
|
+
This is the only way to remove an attribute from an existing SQS queue's configuration.
|
|
263
|
+
:param properties: the properties passed from cloudformation
|
|
264
|
+
"""
|
|
265
|
+
for k, v in self.DEFAULT_ATTRIBUTE_VALUES.items():
|
|
266
|
+
properties.setdefault(k, v)
|
|
267
|
+
|
|
253
268
|
def list(
|
|
254
269
|
self,
|
|
255
270
|
request: ResourceRequest[SQSQueueProperties],
|
|
@@ -78,6 +78,7 @@ from localstack.aws.api.ssm import (
|
|
|
78
78
|
)
|
|
79
79
|
from localstack.aws.connect import connect_to
|
|
80
80
|
from localstack.services.moto import call_moto, call_moto_with_request
|
|
81
|
+
from localstack.state import StateVisitor
|
|
81
82
|
from localstack.utils.aws.arns import extract_resource_from_arn, is_arn
|
|
82
83
|
from localstack.utils.bootstrap import is_api_enabled
|
|
83
84
|
from localstack.utils.collections import remove_attributes
|
|
@@ -105,6 +106,11 @@ class InvalidParameterNameException(ValidationException):
|
|
|
105
106
|
|
|
106
107
|
# TODO: check if _normalize_name(..) calls are still required here
|
|
107
108
|
class SsmProvider(SsmApi, ABC):
|
|
109
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
110
|
+
from moto.ssm.models import ssm_backends
|
|
111
|
+
|
|
112
|
+
visitor.visit(ssm_backends)
|
|
113
|
+
|
|
108
114
|
def get_parameters(
|
|
109
115
|
self,
|
|
110
116
|
context: RequestContext,
|
|
@@ -18,7 +18,7 @@ class ResultPath(EvalComponent):
|
|
|
18
18
|
def _eval_body(self, env: Environment) -> None:
|
|
19
19
|
state_input = env.states.get_input()
|
|
20
20
|
|
|
21
|
-
# Discard task output if there is one, and set the output
|
|
21
|
+
# Discard task output if there is one, and set the output to be the state's input.
|
|
22
22
|
if self.result_path_src is None:
|
|
23
23
|
env.stack.clear()
|
|
24
24
|
env.stack.append(state_input)
|
|
@@ -251,7 +251,6 @@ class ExecutionState(CommonStateField, abc.ABC):
|
|
|
251
251
|
)
|
|
252
252
|
error_output = self._construct_error_output_value(failure_event=failure_event)
|
|
253
253
|
env.states.set_error_output(error_output)
|
|
254
|
-
env.states.set_result(error_output)
|
|
255
254
|
|
|
256
255
|
if self.retry:
|
|
257
256
|
retry_outcome: RetryOutcome = self._handle_retry(
|
localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py
CHANGED
|
@@ -311,7 +311,6 @@ class StateMap(ExecutionState):
|
|
|
311
311
|
failure_event: FailureEvent = self._from_error(env=env, ex=ex)
|
|
312
312
|
error_output = self._construct_error_output_value(failure_event=failure_event)
|
|
313
313
|
env.states.set_error_output(error_output)
|
|
314
|
-
env.states.set_result(error_output)
|
|
315
314
|
|
|
316
315
|
if self.retry:
|
|
317
316
|
retry_outcome: RetryOutcome = self._handle_retry(
|
|
@@ -6,13 +6,13 @@ from localstack.aws.api.lambda_ import InvocationResponse
|
|
|
6
6
|
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.credentials import (
|
|
7
7
|
StateCredentials,
|
|
8
8
|
)
|
|
9
|
-
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.
|
|
10
|
-
|
|
9
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.local_mock_eval_utils import (
|
|
10
|
+
eval_local_mocked_response,
|
|
11
11
|
)
|
|
12
12
|
from localstack.services.stepfunctions.asl.eval.environment import Environment
|
|
13
13
|
from localstack.services.stepfunctions.asl.utils.boto_client import boto_client_for
|
|
14
14
|
from localstack.services.stepfunctions.asl.utils.encoding import to_json_str
|
|
15
|
-
from localstack.services.stepfunctions.
|
|
15
|
+
from localstack.services.stepfunctions.local_mocking.mock_config import LocalMockedResponse
|
|
16
16
|
from localstack.utils.collections import select_from_typed_dict
|
|
17
17
|
from localstack.utils.strings import to_bytes
|
|
18
18
|
|
|
@@ -42,9 +42,9 @@ def _from_payload(payload_streaming_body: IO[bytes]) -> Any | str:
|
|
|
42
42
|
return decoded_data
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def
|
|
46
|
-
mocked_response:
|
|
47
|
-
|
|
45
|
+
def _local_mocked_invoke_lambda_function(env: Environment) -> InvocationResponse:
|
|
46
|
+
mocked_response: LocalMockedResponse = env.get_current_local_mocked_response()
|
|
47
|
+
eval_local_mocked_response(env=env, mocked_response=mocked_response)
|
|
48
48
|
invocation_resp: InvocationResponse = env.stack.pop()
|
|
49
49
|
return invocation_resp
|
|
50
50
|
|
|
@@ -68,8 +68,8 @@ def _invoke_lambda_function(
|
|
|
68
68
|
def execute_lambda_function_integration(
|
|
69
69
|
env: Environment, parameters: dict, region: str, state_credentials: StateCredentials
|
|
70
70
|
) -> None:
|
|
71
|
-
if env.
|
|
72
|
-
invocation_response: InvocationResponse =
|
|
71
|
+
if env.is_local_mocked_mode():
|
|
72
|
+
invocation_response: InvocationResponse = _local_mocked_invoke_lambda_function(env=env)
|
|
73
73
|
else:
|
|
74
74
|
invocation_response: InvocationResponse = _invoke_lambda_function(
|
|
75
75
|
parameters=parameters, region=region, state_credentials=state_credentials
|
|
@@ -10,14 +10,16 @@ from localstack.services.stepfunctions.asl.component.common.error_name.failure_e
|
|
|
10
10
|
)
|
|
11
11
|
from localstack.services.stepfunctions.asl.eval.environment import Environment
|
|
12
12
|
from localstack.services.stepfunctions.asl.eval.event.event_detail import EventDetails
|
|
13
|
-
from localstack.services.stepfunctions.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
from localstack.services.stepfunctions.local_mocking.mock_config import (
|
|
14
|
+
LocalMockedResponse,
|
|
15
|
+
LocalMockedResponseReturn,
|
|
16
|
+
LocalMockedResponseThrow,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def _eval_mocked_response_throw(
|
|
20
|
+
def _eval_mocked_response_throw(
|
|
21
|
+
env: Environment, mocked_response: LocalMockedResponseThrow
|
|
22
|
+
) -> None:
|
|
21
23
|
task_failed_event_details = TaskFailedEventDetails(
|
|
22
24
|
error=mocked_response.error, cause=mocked_response.cause
|
|
23
25
|
)
|
|
@@ -31,15 +33,17 @@ def _eval_mocked_response_throw(env: Environment, mocked_response: MockedRespons
|
|
|
31
33
|
raise FailureEventException(failure_event=failure_event)
|
|
32
34
|
|
|
33
35
|
|
|
34
|
-
def _eval_mocked_response_return(
|
|
36
|
+
def _eval_mocked_response_return(
|
|
37
|
+
env: Environment, mocked_response: LocalMockedResponseReturn
|
|
38
|
+
) -> None:
|
|
35
39
|
payload_copy = copy.deepcopy(mocked_response.payload)
|
|
36
40
|
env.stack.append(payload_copy)
|
|
37
41
|
|
|
38
42
|
|
|
39
|
-
def
|
|
40
|
-
if isinstance(mocked_response,
|
|
43
|
+
def eval_local_mocked_response(env: Environment, mocked_response: LocalMockedResponse) -> None:
|
|
44
|
+
if isinstance(mocked_response, LocalMockedResponseReturn):
|
|
41
45
|
_eval_mocked_response_return(env=env, mocked_response=mocked_response)
|
|
42
|
-
elif isinstance(mocked_response,
|
|
46
|
+
elif isinstance(mocked_response, LocalMockedResponseThrow):
|
|
43
47
|
_eval_mocked_response_throw(env=env, mocked_response=mocked_response)
|
|
44
48
|
else:
|
|
45
49
|
raise RuntimeError(f"Invalid MockedResponse type '{type(mocked_response)}'")
|
|
@@ -33,8 +33,8 @@ from localstack.services.stepfunctions.asl.component.common.error_name.states_er
|
|
|
33
33
|
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.credentials import (
|
|
34
34
|
StateCredentials,
|
|
35
35
|
)
|
|
36
|
-
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.
|
|
37
|
-
|
|
36
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.local_mock_eval_utils import (
|
|
37
|
+
eval_local_mocked_response,
|
|
38
38
|
)
|
|
39
39
|
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.service.resource import (
|
|
40
40
|
ResourceRuntimePart,
|
|
@@ -47,7 +47,7 @@ from localstack.services.stepfunctions.asl.component.state.state_props import St
|
|
|
47
47
|
from localstack.services.stepfunctions.asl.eval.environment import Environment
|
|
48
48
|
from localstack.services.stepfunctions.asl.eval.event.event_detail import EventDetails
|
|
49
49
|
from localstack.services.stepfunctions.asl.utils.encoding import to_json_str
|
|
50
|
-
from localstack.services.stepfunctions.
|
|
50
|
+
from localstack.services.stepfunctions.local_mocking.mock_config import LocalMockedResponse
|
|
51
51
|
from localstack.services.stepfunctions.quotas import is_within_size_quota
|
|
52
52
|
from localstack.utils.strings import camel_to_snake_case, snake_to_camel_case, to_bytes, to_str
|
|
53
53
|
|
|
@@ -356,9 +356,9 @@ class StateTaskService(StateTask, abc.ABC):
|
|
|
356
356
|
normalised_parameters = copy.deepcopy(raw_parameters)
|
|
357
357
|
self._normalise_parameters(normalised_parameters)
|
|
358
358
|
|
|
359
|
-
if env.
|
|
360
|
-
mocked_response:
|
|
361
|
-
|
|
359
|
+
if env.is_local_mocked_mode():
|
|
360
|
+
mocked_response: LocalMockedResponse = env.get_current_local_mocked_response()
|
|
361
|
+
eval_local_mocked_response(env=env, mocked_response=mocked_response)
|
|
362
362
|
else:
|
|
363
363
|
self._eval_service_task(
|
|
364
364
|
env=env,
|
|
@@ -346,7 +346,7 @@ class StateTaskServiceCallback(StateTaskService, abc.ABC):
|
|
|
346
346
|
)
|
|
347
347
|
),
|
|
348
348
|
)
|
|
349
|
-
if not env.
|
|
349
|
+
if not env.is_local_mocked_mode() and not env.is_test_state_mocked_mode():
|
|
350
350
|
self._eval_integration_pattern(
|
|
351
351
|
env=env,
|
|
352
352
|
resource_runtime_part=resource_runtime_part,
|
|
@@ -7,6 +7,9 @@ from localstack.services.stepfunctions.asl.component.common.error_name.failure_e
|
|
|
7
7
|
FailureEventException,
|
|
8
8
|
)
|
|
9
9
|
from localstack.services.stepfunctions.asl.component.state.state import CommonStateField
|
|
10
|
+
from localstack.services.stepfunctions.asl.component.state.state_continue_with import (
|
|
11
|
+
ContinueWithEnd,
|
|
12
|
+
)
|
|
10
13
|
from localstack.services.stepfunctions.asl.component.state.state_fail.cause_decl import CauseDecl
|
|
11
14
|
from localstack.services.stepfunctions.asl.component.state.state_fail.error_decl import ErrorDecl
|
|
12
15
|
from localstack.services.stepfunctions.asl.component.state.state_props import StateProps
|
|
@@ -27,6 +30,7 @@ class StateFail(CommonStateField):
|
|
|
27
30
|
super().from_state_props(state_props)
|
|
28
31
|
self.cause = state_props.get(CauseDecl)
|
|
29
32
|
self.error = state_props.get(ErrorDecl)
|
|
33
|
+
self.continue_with = ContinueWithEnd()
|
|
30
34
|
|
|
31
35
|
def _eval_state(self, env: Environment) -> None:
|
|
32
36
|
task_failed_event_details = TaskFailedEventDetails()
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import copy
|
|
3
|
+
from typing import Generic, TypeVar
|
|
4
|
+
|
|
5
|
+
from localstack.services.stepfunctions.asl.component.eval_component import EvalComponent
|
|
6
|
+
from localstack.services.stepfunctions.asl.component.state.state import CommonStateField
|
|
7
|
+
from localstack.services.stepfunctions.asl.component.state.state_continue_with import (
|
|
8
|
+
ContinueWithNext,
|
|
9
|
+
)
|
|
10
|
+
from localstack.services.stepfunctions.asl.eval.test_state.environment import TestStateEnvironment
|
|
11
|
+
from localstack.services.stepfunctions.asl.utils.encoding import to_json_str
|
|
12
|
+
from localstack.services.stepfunctions.backend.test_state.test_state_mock import (
|
|
13
|
+
TestStateResponseReturn,
|
|
14
|
+
TestStateResponseThrow,
|
|
15
|
+
eval_mocked_response_throw,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
T = TypeVar("T", bound=CommonStateField)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MockedBaseState(Generic[T], abc.ABC):
|
|
22
|
+
is_single_state: bool
|
|
23
|
+
_wrapped: T
|
|
24
|
+
|
|
25
|
+
def __init__(self, wrapped: T):
|
|
26
|
+
super().__init__()
|
|
27
|
+
self._wrapped = wrapped
|
|
28
|
+
self.apply_patches()
|
|
29
|
+
|
|
30
|
+
def apply_patches(self):
|
|
31
|
+
self._apply_patches()
|
|
32
|
+
|
|
33
|
+
original_eval_body = self._wrapped._eval_body
|
|
34
|
+
self._wrapped._eval_body = self.wrap_with_post_return(
|
|
35
|
+
original_eval_body, self.stop_execution
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
@abc.abstractmethod
|
|
39
|
+
def _apply_patches(self): ...
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def wrap(cls, state: T, is_single_state: bool = False) -> T:
|
|
43
|
+
cls.is_single_state = is_single_state
|
|
44
|
+
cls._wrapped = state
|
|
45
|
+
return cls(state)._wrapped
|
|
46
|
+
|
|
47
|
+
def __getattr__(self, attr: str):
|
|
48
|
+
return getattr(self._wrapped, attr)
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def before_mock(self, env: TestStateEnvironment):
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def do_mock(self, env: TestStateEnvironment):
|
|
56
|
+
mocked_response = env.mock.get_next_result()
|
|
57
|
+
if not mocked_response:
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
if isinstance(mocked_response, TestStateResponseThrow):
|
|
61
|
+
eval_mocked_response_throw(env, mocked_response)
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
if isinstance(mocked_response, TestStateResponseReturn):
|
|
65
|
+
result_copy = copy.deepcopy(mocked_response.payload)
|
|
66
|
+
env.stack.append(result_copy)
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def after_mock(self, env: TestStateEnvironment):
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def wrap_with_mock(cls, original_method):
|
|
74
|
+
def wrapper(env: TestStateEnvironment, *args, **kwargs):
|
|
75
|
+
if not env.mock.is_mocked():
|
|
76
|
+
original_method(env, *args, **kwargs)
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
cls.before_mock(env)
|
|
80
|
+
try:
|
|
81
|
+
cls.do_mock(env)
|
|
82
|
+
finally:
|
|
83
|
+
cls.after_mock(env)
|
|
84
|
+
|
|
85
|
+
return wrapper
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def wrap_with_post_return(method, post_return_fn):
|
|
89
|
+
def wrapper(env: TestStateEnvironment, *args, **kwargs):
|
|
90
|
+
try:
|
|
91
|
+
method(env, *args, **kwargs)
|
|
92
|
+
finally:
|
|
93
|
+
post_return_fn(env)
|
|
94
|
+
|
|
95
|
+
return wrapper
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def _eval_with_inspect(component: EvalComponent, key: str):
|
|
99
|
+
if not component:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
eval_body_fn = component._eval_body
|
|
103
|
+
|
|
104
|
+
def _update(env: TestStateEnvironment, *args, **kwargs):
|
|
105
|
+
# if inspectionData already populated, don't execute again
|
|
106
|
+
if key in env.inspection_data:
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
eval_body_fn(env, *args, **kwargs)
|
|
110
|
+
result = env.stack[-1]
|
|
111
|
+
env.inspection_data[key] = to_json_str(result)
|
|
112
|
+
|
|
113
|
+
component._eval_body = MockedBaseState.wrap_with_post_return(eval_body_fn, _update)
|
|
114
|
+
|
|
115
|
+
def stop_execution(self, env: TestStateEnvironment):
|
|
116
|
+
if isinstance(self._wrapped.continue_with, ContinueWithNext):
|
|
117
|
+
if next_state := self._wrapped.continue_with.next_state:
|
|
118
|
+
env.set_choice_selected(next_state.name)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from localstack.services.stepfunctions.asl.component.state.state import CommonStateField
|
|
2
|
+
from localstack.services.stepfunctions.asl.component.state.state_choice.state_choice import (
|
|
3
|
+
StateChoice,
|
|
4
|
+
)
|
|
5
|
+
from localstack.services.stepfunctions.asl.component.state.state_continue_with import (
|
|
6
|
+
ContinueWithEnd,
|
|
7
|
+
)
|
|
8
|
+
from localstack.services.stepfunctions.asl.component.state.state_fail.state_fail import StateFail
|
|
9
|
+
from localstack.services.stepfunctions.asl.component.state.state_pass.state_pass import StatePass
|
|
10
|
+
from localstack.services.stepfunctions.asl.component.state.state_succeed.state_succeed import (
|
|
11
|
+
StateSucceed,
|
|
12
|
+
)
|
|
13
|
+
from localstack.services.stepfunctions.asl.component.test_state.state.base_mock import (
|
|
14
|
+
MockedBaseState,
|
|
15
|
+
)
|
|
16
|
+
from localstack.services.stepfunctions.asl.eval.test_state.environment import TestStateEnvironment
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MockedCommonState(MockedBaseState[CommonStateField]):
|
|
20
|
+
def add_inspection_data(self, env: TestStateEnvironment):
|
|
21
|
+
state = self._wrapped
|
|
22
|
+
|
|
23
|
+
if not isinstance(state, StatePass):
|
|
24
|
+
if not self.is_single_state:
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
if "afterInputPath" not in env.inspection_data:
|
|
28
|
+
env.inspection_data["afterInputPath"] = env.states.get_input()
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
# If not a terminal state, only populate inspection data from pre-processor.
|
|
32
|
+
if not isinstance(self._wrapped.continue_with, ContinueWithEnd):
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
if state.result:
|
|
36
|
+
# TODO: investigate interactions between these inspectionData field types.
|
|
37
|
+
# i.e parity tests shows that if "Result" is defined, 'afterInputPath' and 'afterParameters'
|
|
38
|
+
# cannot be present in the inspection data.
|
|
39
|
+
env.inspection_data.pop("afterInputPath", None)
|
|
40
|
+
env.inspection_data.pop("afterParameters", None)
|
|
41
|
+
|
|
42
|
+
if "afterResultSelector" not in env.inspection_data:
|
|
43
|
+
env.inspection_data["afterResultSelector"] = state.result.result_obj
|
|
44
|
+
|
|
45
|
+
if "afterResultPath" not in env.inspection_data:
|
|
46
|
+
env.inspection_data["afterResultPath"] = env.inspection_data.get(
|
|
47
|
+
"afterResultSelector", env.states.get_input()
|
|
48
|
+
)
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
if "afterInputPath" not in env.inspection_data:
|
|
52
|
+
env.inspection_data["afterInputPath"] = env.states.get_input()
|
|
53
|
+
|
|
54
|
+
if "afterParameters" not in env.inspection_data:
|
|
55
|
+
env.inspection_data["afterParameters"] = env.inspection_data.get(
|
|
56
|
+
"afterInputPath", env.states.get_input()
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if "afterResultSelector" not in env.inspection_data:
|
|
60
|
+
env.inspection_data["afterResultSelector"] = env.inspection_data["afterParameters"]
|
|
61
|
+
|
|
62
|
+
if "afterResultPath" not in env.inspection_data:
|
|
63
|
+
env.inspection_data["afterResultPath"] = env.inspection_data.get(
|
|
64
|
+
"afterResultSelector", env.states.get_input()
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def _apply_patches(self):
|
|
68
|
+
if not isinstance(self._wrapped, (StatePass, StateFail, StateChoice, StateSucceed)):
|
|
69
|
+
raise ValueError("Needs to be a Pass, Fail, Choice, or Succeed state.")
|
|
70
|
+
|
|
71
|
+
original_eval_body = self.wrap_with_mock(self._wrapped._eval_body)
|
|
72
|
+
|
|
73
|
+
def mock_eval_execution(env: TestStateEnvironment):
|
|
74
|
+
original_eval_body(env)
|
|
75
|
+
env.set_choice_selected(env.next_state_name)
|
|
76
|
+
|
|
77
|
+
mock_eval_execution = self.wrap_with_post_return(
|
|
78
|
+
method=mock_eval_execution,
|
|
79
|
+
post_return_fn=self.add_inspection_data,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
self._wrapped._eval_body = mock_eval_execution
|