localstack-core 4.11.2.dev14__py3-none-any.whl → 4.12.1.dev25__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/ec2/__init__.py +13 -0
- localstack/aws/api/iam/__init__.py +1 -0
- localstack/aws/api/lambda_/__init__.py +616 -0
- localstack/aws/api/logs/__init__.py +188 -0
- localstack/aws/api/opensearch/__init__.py +11 -0
- localstack/aws/api/route53/__init__.py +3 -0
- localstack/aws/api/s3/__init__.py +2 -0
- localstack/aws/api/s3control/__init__.py +19 -0
- localstack/aws/api/secretsmanager/__init__.py +9 -0
- localstack/aws/connect.py +35 -15
- localstack/aws/protocol/parser.py +6 -1
- localstack/aws/spec-patches.json +0 -38
- localstack/config.py +8 -0
- localstack/constants.py +3 -0
- localstack/dev/kubernetes/__main__.py +39 -14
- localstack/runtime/analytics.py +11 -0
- localstack/services/acm/provider.py +13 -1
- localstack/services/apigateway/legacy/provider.py +25 -4
- localstack/services/cloudformation/engine/v2/change_set_model.py +9 -0
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +3 -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/models.py +10 -2
- localstack/services/cloudwatch/provider_v2.py +15 -20
- localstack/services/kinesis/packages.py +1 -1
- localstack/services/kms/models.py +6 -2
- localstack/services/lambda_/analytics.py +11 -2
- localstack/services/lambda_/invocation/event_manager.py +15 -11
- localstack/services/lambda_/invocation/lambda_models.py +4 -0
- localstack/services/lambda_/invocation/lambda_service.py +11 -0
- localstack/services/lambda_/provider.py +70 -13
- localstack/services/opensearch/packages.py +34 -20
- 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 +25 -30
- localstack/services/s3/utils.py +46 -1
- localstack/services/s3control/provider.py +6 -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 +13 -0
- localstack/services/sns/provider.py +5 -0
- localstack/services/sns/v2/models.py +4 -0
- localstack/services/sns/v2/provider.py +145 -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/static_analyser/test_state/test_state_analyser.py +193 -107
- localstack/services/stepfunctions/backend/execution.py +4 -5
- localstack/services/stepfunctions/provider.py +21 -14
- 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/utils/aws/client_types.py +2 -4
- localstack/utils/bootstrap.py +2 -2
- localstack/utils/catalog/catalog.py +3 -2
- 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.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/METADATA +6 -6
- {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/RECORD +81 -80
- localstack_core-4.12.1.dev25.dist-info/plux.json +1 -0
- localstack_core-4.11.2.dev14.dist-info/plux.json +0 -1
- {localstack_core-4.11.2.dev14.data → localstack_core-4.12.1.dev25.data}/scripts/localstack +0 -0
- {localstack_core-4.11.2.dev14.data → localstack_core-4.12.1.dev25.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.11.2.dev14.data → localstack_core-4.12.1.dev25.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/WHEEL +0 -0
- {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/entry_points.txt +0 -0
- {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/top_level.txt +0 -0
|
@@ -12,18 +12,35 @@ from botocore.model import (
|
|
|
12
12
|
|
|
13
13
|
from localstack.aws.api.stepfunctions import (
|
|
14
14
|
Definition,
|
|
15
|
+
InvalidDefinition,
|
|
15
16
|
MockInput,
|
|
16
17
|
MockResponseValidationMode,
|
|
17
18
|
StateName,
|
|
19
|
+
TestStateInput,
|
|
18
20
|
ValidationException,
|
|
19
21
|
)
|
|
20
22
|
from localstack.services.stepfunctions.asl.antlr.runtime.ASLParser import ASLParser
|
|
23
|
+
from localstack.services.stepfunctions.asl.component.state.state import CommonStateField
|
|
24
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.state_map import (
|
|
25
|
+
StateMap,
|
|
26
|
+
)
|
|
27
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.state_parallel.state_parallel import (
|
|
28
|
+
StateParallel,
|
|
29
|
+
)
|
|
21
30
|
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.service.state_task_service import (
|
|
22
31
|
StateTaskService,
|
|
23
32
|
)
|
|
24
33
|
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.service.state_task_service_api_gateway import (
|
|
25
34
|
StateTaskServiceApiGateway,
|
|
26
35
|
)
|
|
36
|
+
from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.state_task import (
|
|
37
|
+
StateTask,
|
|
38
|
+
)
|
|
39
|
+
from localstack.services.stepfunctions.asl.component.state.state_fail.state_fail import StateFail
|
|
40
|
+
from localstack.services.stepfunctions.asl.component.state.state_pass.state_pass import StatePass
|
|
41
|
+
from localstack.services.stepfunctions.asl.component.state.state_succeed.state_succeed import (
|
|
42
|
+
StateSucceed,
|
|
43
|
+
)
|
|
27
44
|
from localstack.services.stepfunctions.asl.component.state.state_type import StateType
|
|
28
45
|
from localstack.services.stepfunctions.asl.component.test_state.program.test_state_program import (
|
|
29
46
|
TestStateProgram,
|
|
@@ -59,10 +76,92 @@ class TestStateStaticAnalyser(StaticAnalyser):
|
|
|
59
76
|
return test_program.test_state is not None
|
|
60
77
|
|
|
61
78
|
@staticmethod
|
|
62
|
-
def
|
|
79
|
+
def validate_role_arn_required(
|
|
80
|
+
mock_input: MockInput, definition: Definition, state_name: StateName
|
|
81
|
+
) -> None:
|
|
63
82
|
test_program, _ = TestStateAmazonStateLanguageParser.parse(definition, state_name)
|
|
64
83
|
test_state = test_program.test_state
|
|
84
|
+
if isinstance(test_state, StateTask) and mock_input is None:
|
|
85
|
+
raise ValidationException("RoleArn must be specified when testing a Task state")
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def validate_mock(test_state_input: TestStateInput) -> None:
|
|
89
|
+
test_program, _ = TestStateAmazonStateLanguageParser.parse(
|
|
90
|
+
test_state_input.get("definition"), test_state_input.get("stateName")
|
|
91
|
+
)
|
|
92
|
+
test_state = test_program.test_state
|
|
93
|
+
mock_input = test_state_input.get("mock")
|
|
94
|
+
|
|
95
|
+
TestStateStaticAnalyser.validate_test_state_allows_mocking(
|
|
96
|
+
mock_input=mock_input, test_state=test_state
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if mock_input is None:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
if test_state_input.get("revealSecrets"):
|
|
103
|
+
raise ValidationException(
|
|
104
|
+
"TestState does not support RevealSecrets when a mock is specified."
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if {"result", "errorOutput"} <= mock_input.keys():
|
|
108
|
+
raise ValidationException(
|
|
109
|
+
"A test mock should have only one of the following fields: [result, errorOutput]."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
mock_result_raw = mock_input.get("result")
|
|
113
|
+
if mock_result_raw is None:
|
|
114
|
+
return
|
|
115
|
+
try:
|
|
116
|
+
mock_result = json.loads(mock_result_raw)
|
|
117
|
+
except json.JSONDecodeError:
|
|
118
|
+
raise ValidationException("Mocked result must be valid JSON")
|
|
119
|
+
|
|
120
|
+
if isinstance(test_state, StateMap):
|
|
121
|
+
TestStateStaticAnalyser.validate_mock_result_matches_map_definition(
|
|
122
|
+
mock_result=mock_result, test_state=test_state
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if isinstance(test_state, StateTaskService):
|
|
126
|
+
field_validation_mode = mock_input.get(
|
|
127
|
+
"fieldValidationMode", MockResponseValidationMode.STRICT
|
|
128
|
+
)
|
|
129
|
+
TestStateStaticAnalyser.validate_mock_result_matches_api_shape(
|
|
130
|
+
mock_result=mock_result,
|
|
131
|
+
field_validation_mode=field_validation_mode,
|
|
132
|
+
test_state=test_state,
|
|
133
|
+
)
|
|
65
134
|
|
|
135
|
+
@staticmethod
|
|
136
|
+
def validate_test_state_allows_mocking(
|
|
137
|
+
mock_input: MockInput, test_state: CommonStateField
|
|
138
|
+
) -> None:
|
|
139
|
+
if mock_input is None and isinstance(test_state, (StateMap, StateParallel)):
|
|
140
|
+
# This is a literal message when a Map or Parallel state is not accompanied by a mock in a test state request.
|
|
141
|
+
# The message is the same for both cases and is not parametrised anyhow.
|
|
142
|
+
raise InvalidDefinition(
|
|
143
|
+
"TestState API does not support Map or Parallel states. Supported state types include: [Task, Wait, Pass, Succeed, Fail, Choice]"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if mock_input is not None and isinstance(test_state, (StatePass, StateFail, StateSucceed)):
|
|
147
|
+
raise ValidationException(
|
|
148
|
+
f"State type '{test_state.state_type.name}' is not supported when a mock is specified"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def validate_mock_result_matches_map_definition(mock_result: Any, test_state: StateMap):
|
|
153
|
+
if test_state.result_writer is not None and not isinstance(mock_result, dict):
|
|
154
|
+
raise ValidationException("Mocked result must be a JSON object.")
|
|
155
|
+
|
|
156
|
+
if test_state.result_writer is None and not isinstance(mock_result, list):
|
|
157
|
+
raise ValidationException("Mocked result must be an array.")
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def validate_mock_result_matches_api_shape(
|
|
161
|
+
mock_result: Any,
|
|
162
|
+
field_validation_mode: MockResponseValidationMode,
|
|
163
|
+
test_state: StateTaskService,
|
|
164
|
+
):
|
|
66
165
|
# apigateway:invoke: has no equivalent in the AWS SDK service integration.
|
|
67
166
|
# Hence, the validation against botocore doesn't apply.
|
|
68
167
|
# See the note in https://docs.aws.amazon.com/step-functions/latest/dg/connect-api-gateway.html
|
|
@@ -70,119 +169,106 @@ class TestStateStaticAnalyser(StaticAnalyser):
|
|
|
70
169
|
if isinstance(test_state, StateTaskServiceApiGateway):
|
|
71
170
|
return
|
|
72
171
|
|
|
73
|
-
if
|
|
74
|
-
|
|
75
|
-
|
|
172
|
+
if field_validation_mode == MockResponseValidationMode.NONE:
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
boto_service_name = test_state._get_boto_service_name()
|
|
176
|
+
service_action_name = test_state._get_boto_service_action()
|
|
177
|
+
output_shape = test_state._get_boto_operation_model(
|
|
178
|
+
boto_service_name=boto_service_name, service_action_name=service_action_name
|
|
179
|
+
).output_shape
|
|
180
|
+
|
|
181
|
+
# If the operation has no output, there's nothing to validate
|
|
182
|
+
if output_shape is None:
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
def _raise_type_error(expected_type: str, field_name: str) -> None:
|
|
186
|
+
raise ValidationException(
|
|
187
|
+
f"Mock result schema validation error: Field '{field_name}' must be {expected_type}"
|
|
76
188
|
)
|
|
77
|
-
|
|
78
|
-
|
|
189
|
+
|
|
190
|
+
def _validate_value(value: Any, shape: Shape, field_name: str | None = None) -> None:
|
|
191
|
+
# Document type accepts any JSON value
|
|
192
|
+
if shape.type_name == "document":
|
|
79
193
|
return
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
194
|
+
|
|
195
|
+
if isinstance(shape, StructureShape):
|
|
196
|
+
if not isinstance(value, dict):
|
|
197
|
+
# this is a defensive check, the mock result is loaded from JSON before, so should always be a dict
|
|
198
|
+
raise ValidationException(
|
|
199
|
+
f"Mock result must be a valid JSON object, but got '{type(value)}' instead"
|
|
200
|
+
)
|
|
201
|
+
# Build a mapping from SFN-normalised member keys -> botocore member shapes
|
|
202
|
+
members = shape.members
|
|
203
|
+
sfn_key_to_member_shape: dict[str, Shape] = {
|
|
204
|
+
StateTaskService._to_sfn_cased(member_key): member_shape
|
|
205
|
+
for member_key, member_shape in members.items()
|
|
206
|
+
}
|
|
207
|
+
if field_validation_mode == MockResponseValidationMode.STRICT:
|
|
208
|
+
# Ensure required members are present, using SFN-normalised keys
|
|
209
|
+
for required_key in shape.required_members:
|
|
210
|
+
sfn_required_key = StateTaskService._to_sfn_cased(required_key)
|
|
211
|
+
if sfn_required_key not in value:
|
|
212
|
+
raise ValidationException(
|
|
213
|
+
f"Mock result schema validation error: Required field '{sfn_required_key}' is missing"
|
|
214
|
+
)
|
|
215
|
+
# Validate present fields (match SFN-normalised keys to member shapes)
|
|
216
|
+
for mock_field_name, mock_field_value in value.items():
|
|
217
|
+
member_shape = sfn_key_to_member_shape.get(mock_field_name)
|
|
218
|
+
if member_shape is None:
|
|
219
|
+
# Fields that are present in mock but are not in the API spec should not raise validation errors - forward compatibility
|
|
220
|
+
continue
|
|
221
|
+
_validate_value(mock_field_value, member_shape, mock_field_name)
|
|
85
222
|
return
|
|
86
223
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
224
|
+
if isinstance(shape, ListShape):
|
|
225
|
+
if not isinstance(value, list):
|
|
226
|
+
_raise_type_error("an array", field_name)
|
|
227
|
+
member_shape = shape.member
|
|
228
|
+
for list_item in value:
|
|
229
|
+
_validate_value(list_item, member_shape, field_name)
|
|
230
|
+
return
|
|
92
231
|
|
|
93
|
-
|
|
94
|
-
|
|
232
|
+
if isinstance(shape, MapShape):
|
|
233
|
+
if not isinstance(value, dict):
|
|
234
|
+
_raise_type_error("an object", field_name)
|
|
235
|
+
value_shape = shape.value
|
|
236
|
+
for _, map_item_value in value.items():
|
|
237
|
+
_validate_value(map_item_value, value_shape, field_name)
|
|
95
238
|
return
|
|
96
239
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
member_shape = sfn_key_to_member_shape.get(mock_field_name)
|
|
130
|
-
if member_shape is None:
|
|
131
|
-
# Fields that are present in mock but are not in the API spec should not raise validation errors - forward compatibility
|
|
132
|
-
continue
|
|
133
|
-
_validate_value(mock_field_value, member_shape, mock_field_name)
|
|
134
|
-
return
|
|
135
|
-
|
|
136
|
-
if isinstance(shape, ListShape):
|
|
137
|
-
if not isinstance(value, list):
|
|
138
|
-
_raise_type_error("an array", field_name)
|
|
139
|
-
member_shape = shape.member
|
|
140
|
-
for list_item in value:
|
|
141
|
-
_validate_value(list_item, member_shape, field_name)
|
|
142
|
-
return
|
|
143
|
-
|
|
144
|
-
if isinstance(shape, MapShape):
|
|
145
|
-
if not isinstance(value, dict):
|
|
146
|
-
_raise_type_error("an object", field_name)
|
|
147
|
-
value_shape = shape.value
|
|
148
|
-
for _, map_item_value in value.items():
|
|
149
|
-
_validate_value(map_item_value, value_shape, field_name)
|
|
150
|
-
return
|
|
151
|
-
|
|
152
|
-
# Primitive shapes and others
|
|
153
|
-
type_name = shape.type_name
|
|
154
|
-
match type_name:
|
|
155
|
-
case "string" | "timestamp":
|
|
156
|
-
if not isinstance(value, str):
|
|
157
|
-
_raise_type_error("a string", field_name)
|
|
158
|
-
# Validate enum if present
|
|
159
|
-
if isinstance(shape, StringShape):
|
|
160
|
-
enum = getattr(shape, "enum", None)
|
|
161
|
-
if enum and value not in enum:
|
|
162
|
-
raise ValidationException(
|
|
163
|
-
f"Mock result schema validation error: Field '{field_name}' is not an expected value"
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
case "integer" | "long":
|
|
167
|
-
if not isinstance(value, int) or isinstance(value, bool):
|
|
168
|
-
_raise_type_error("a number", field_name)
|
|
169
|
-
|
|
170
|
-
case "float" | "double":
|
|
171
|
-
if not (isinstance(value, (int, float)) or isinstance(value, bool)):
|
|
172
|
-
_raise_type_error("a number", field_name)
|
|
173
|
-
|
|
174
|
-
case "boolean":
|
|
175
|
-
if not isinstance(value, bool):
|
|
176
|
-
_raise_type_error("a boolean", field_name)
|
|
177
|
-
|
|
178
|
-
case "blob":
|
|
179
|
-
if not (isinstance(value, (str, bytes))):
|
|
180
|
-
_raise_type_error("a string", field_name)
|
|
181
|
-
|
|
182
|
-
# Perform validation against the output shape
|
|
183
|
-
_validate_value(mock_result, output_shape)
|
|
184
|
-
# Non-service tasks or other cases: nothing to validate
|
|
185
|
-
return
|
|
240
|
+
# Primitive shapes and others
|
|
241
|
+
type_name = shape.type_name
|
|
242
|
+
match type_name:
|
|
243
|
+
case "string" | "timestamp":
|
|
244
|
+
if not isinstance(value, str):
|
|
245
|
+
_raise_type_error("a string", field_name)
|
|
246
|
+
# Validate enum if present
|
|
247
|
+
if isinstance(shape, StringShape):
|
|
248
|
+
enum = getattr(shape, "enum", None)
|
|
249
|
+
if enum and value not in enum:
|
|
250
|
+
raise ValidationException(
|
|
251
|
+
f"Mock result schema validation error: Field '{field_name}' is not an expected value"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
case "integer" | "long":
|
|
255
|
+
if not isinstance(value, int) or isinstance(value, bool):
|
|
256
|
+
_raise_type_error("a number", field_name)
|
|
257
|
+
|
|
258
|
+
case "float" | "double":
|
|
259
|
+
if not (isinstance(value, (int, float)) or isinstance(value, bool)):
|
|
260
|
+
_raise_type_error("a number", field_name)
|
|
261
|
+
|
|
262
|
+
case "boolean":
|
|
263
|
+
if not isinstance(value, bool):
|
|
264
|
+
_raise_type_error("a boolean", field_name)
|
|
265
|
+
|
|
266
|
+
case "blob":
|
|
267
|
+
if not (isinstance(value, (str, bytes))):
|
|
268
|
+
_raise_type_error("a string", field_name)
|
|
269
|
+
|
|
270
|
+
# Perform validation against the output shape
|
|
271
|
+
_validate_value(mock_result, output_shape)
|
|
186
272
|
|
|
187
273
|
def analyse(self, definition: str) -> None:
|
|
188
274
|
_, parser_rule_context = TestStateAmazonStateLanguageParser.parse(
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import datetime
|
|
4
|
-
import json
|
|
5
4
|
import logging
|
|
6
|
-
from typing import Final
|
|
5
|
+
from typing import Any, Final
|
|
7
6
|
|
|
8
7
|
from localstack.aws.api.events import PutEventsRequestEntry
|
|
9
8
|
from localstack.aws.api.stepfunctions import (
|
|
@@ -111,7 +110,7 @@ class Execution:
|
|
|
111
110
|
local_mock_test_case: Final[LocalMockTestCase | None]
|
|
112
111
|
|
|
113
112
|
start_date: Final[Timestamp]
|
|
114
|
-
input_data: Final[
|
|
113
|
+
input_data: Final[dict[str, Any] | None]
|
|
115
114
|
input_details: Final[CloudWatchEventsExecutionDataDetails | None]
|
|
116
115
|
trace_header: Final[TraceHeader | None]
|
|
117
116
|
_cloud_watch_logging_session: Final[CloudWatchLoggingSession | None]
|
|
@@ -119,7 +118,7 @@ class Execution:
|
|
|
119
118
|
exec_status: ExecutionStatus | None
|
|
120
119
|
stop_date: Timestamp | None
|
|
121
120
|
|
|
122
|
-
output:
|
|
121
|
+
output: dict[str, Any] | None
|
|
123
122
|
output_details: CloudWatchEventsExecutionDataDetails | None
|
|
124
123
|
|
|
125
124
|
error: SensitiveError | None
|
|
@@ -141,7 +140,7 @@ class Execution:
|
|
|
141
140
|
start_date: Timestamp,
|
|
142
141
|
cloud_watch_logging_session: CloudWatchLoggingSession | None,
|
|
143
142
|
activity_store: dict[Arn, Activity],
|
|
144
|
-
input_data:
|
|
143
|
+
input_data: dict[str, Any] | None = None,
|
|
145
144
|
trace_header: TraceHeader | None = None,
|
|
146
145
|
state_machine_alias_arn: Arn | None = None,
|
|
147
146
|
local_mock_test_case: LocalMockTestCase | None = None,
|
|
@@ -57,7 +57,6 @@ from localstack.aws.api.stepfunctions import (
|
|
|
57
57
|
LongArn,
|
|
58
58
|
MaxConcurrency,
|
|
59
59
|
MissingRequiredParameter,
|
|
60
|
-
MockInput,
|
|
61
60
|
Name,
|
|
62
61
|
PageSize,
|
|
63
62
|
PageToken,
|
|
@@ -162,6 +161,7 @@ from localstack.services.stepfunctions.stepfunctions_utils import (
|
|
|
162
161
|
normalise_max_results,
|
|
163
162
|
)
|
|
164
163
|
from localstack.state import StateVisitor
|
|
164
|
+
from localstack.utils.aws import arns
|
|
165
165
|
from localstack.utils.aws.arns import (
|
|
166
166
|
ARN_PARTITION_REGEX,
|
|
167
167
|
stepfunctions_activity_arn,
|
|
@@ -242,12 +242,6 @@ class StepFunctionsProvider(StepfunctionsApi, ServiceLifecycleHook):
|
|
|
242
242
|
f"Invalid Arn: 'Resource type not valid in this context: {lower_resource_type}'"
|
|
243
243
|
)
|
|
244
244
|
|
|
245
|
-
@staticmethod
|
|
246
|
-
def _validate_test_state_mock_input(mock_input: MockInput) -> None:
|
|
247
|
-
if {"result", "errorOutput"} <= mock_input.keys():
|
|
248
|
-
# FIXME create proper error
|
|
249
|
-
raise ValidationException("Cannot define both 'result' and 'errorOutput'")
|
|
250
|
-
|
|
251
245
|
@staticmethod
|
|
252
246
|
def _validate_activity_name(name: str) -> None:
|
|
253
247
|
# The activity name is validated according to the AWS StepFunctions documentation, the name should not contain:
|
|
@@ -1514,11 +1508,7 @@ class StepFunctionsProvider(StepfunctionsApi, ServiceLifecycleHook):
|
|
|
1514
1508
|
raise ValidationException("State not found in definition")
|
|
1515
1509
|
|
|
1516
1510
|
mock_input = request.get("mock")
|
|
1517
|
-
|
|
1518
|
-
self._validate_test_state_mock_input(mock_input)
|
|
1519
|
-
TestStateStaticAnalyser.validate_mock(
|
|
1520
|
-
mock_input=mock_input, definition=definition, state_name=state_name
|
|
1521
|
-
)
|
|
1511
|
+
TestStateStaticAnalyser.validate_mock(test_state_input=request)
|
|
1522
1512
|
|
|
1523
1513
|
if state_configuration := request.get("stateConfiguration"):
|
|
1524
1514
|
# TODO: Add validations for this i.e assert len(input) <= failureCount
|
|
@@ -1543,10 +1533,27 @@ class StepFunctionsProvider(StepfunctionsApi, ServiceLifecycleHook):
|
|
|
1543
1533
|
arn = stepfunctions_state_machine_arn(
|
|
1544
1534
|
name=name, account_id=context.account_id, region_name=context.region
|
|
1545
1535
|
)
|
|
1536
|
+
role_arn = request.get("roleArn")
|
|
1537
|
+
if role_arn is None:
|
|
1538
|
+
TestStateStaticAnalyser.validate_role_arn_required(
|
|
1539
|
+
mock_input=mock_input, definition=definition, state_name=state_name
|
|
1540
|
+
)
|
|
1541
|
+
# HACK: Added dummy role ARN because it is a required field in Execution.
|
|
1542
|
+
# To allow optional roleArn for the test state but preserve the mandatory one for regular executions
|
|
1543
|
+
# we likely need to remove inheritance TestStateExecution(Execution) in favor of composition.
|
|
1544
|
+
# TestState execution starts to have too many simplifications compared to a regular execution
|
|
1545
|
+
# which renders the inheritance mechanism harmful.
|
|
1546
|
+
# TODO make role_arn optional in TestStateExecution
|
|
1547
|
+
role_arn = arns.iam_role_arn(
|
|
1548
|
+
role_name=f"RoleFor-{name}",
|
|
1549
|
+
account_id=context.account_id,
|
|
1550
|
+
region_name=context.region,
|
|
1551
|
+
)
|
|
1552
|
+
|
|
1546
1553
|
state_machine = TestStateMachine(
|
|
1547
1554
|
name=name,
|
|
1548
1555
|
arn=arn,
|
|
1549
|
-
role_arn=
|
|
1556
|
+
role_arn=role_arn,
|
|
1550
1557
|
definition=request["definition"],
|
|
1551
1558
|
)
|
|
1552
1559
|
|
|
@@ -1561,7 +1568,7 @@ class StepFunctionsProvider(StepfunctionsApi, ServiceLifecycleHook):
|
|
|
1561
1568
|
|
|
1562
1569
|
execution = TestStateExecution(
|
|
1563
1570
|
name=exec_name,
|
|
1564
|
-
role_arn=
|
|
1571
|
+
role_arn=role_arn,
|
|
1565
1572
|
exec_arn=exec_arn,
|
|
1566
1573
|
account_id=context.account_id,
|
|
1567
1574
|
region_name=context.region,
|
|
@@ -23,6 +23,7 @@ from localstack.services.iam.iam_patches import apply_iam_patches
|
|
|
23
23
|
from localstack.services.moto import call_moto
|
|
24
24
|
from localstack.services.plugins import ServiceLifecycleHook
|
|
25
25
|
from localstack.services.sts.models import SessionConfig, sts_stores
|
|
26
|
+
from localstack.state import StateVisitor
|
|
26
27
|
from localstack.utils.aws.arns import extract_account_id_from_arn
|
|
27
28
|
from localstack.utils.aws.request_context import extract_access_key_id_from_auth_header
|
|
28
29
|
|
|
@@ -50,6 +51,12 @@ class StsProvider(StsApi, ServiceLifecycleHook):
|
|
|
50
51
|
def __init__(self):
|
|
51
52
|
apply_iam_patches()
|
|
52
53
|
|
|
54
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
55
|
+
from moto.sts.models import sts_backends
|
|
56
|
+
|
|
57
|
+
visitor.visit(sts_backends)
|
|
58
|
+
visitor.visit(sts_stores)
|
|
59
|
+
|
|
53
60
|
def get_caller_identity(self, context: RequestContext, **kwargs) -> GetCallerIdentityResponse:
|
|
54
61
|
response = call_moto(context)
|
|
55
62
|
if "user/moto" in response["Arn"] and "sts" in response["Arn"]:
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
from abc import ABC
|
|
2
2
|
|
|
3
3
|
from localstack.aws.api.support import SupportApi
|
|
4
|
+
from localstack.state import StateVisitor
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class SupportProvider(SupportApi, ABC):
|
|
7
|
-
|
|
8
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
9
|
+
from moto.support.models import support_backends
|
|
10
|
+
|
|
11
|
+
visitor.visit(support_backends)
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
from abc import ABC
|
|
2
2
|
|
|
3
3
|
from localstack.aws.api.swf import SwfApi
|
|
4
|
+
from localstack.state import StateVisitor
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class SWFProvider(SwfApi, ABC):
|
|
7
|
-
|
|
8
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
9
|
+
from moto.swf.models import swf_backends
|
|
10
|
+
|
|
11
|
+
visitor.visit(swf_backends)
|
|
@@ -38,6 +38,7 @@ from localstack.services.s3.utils import (
|
|
|
38
38
|
)
|
|
39
39
|
from localstack.services.transcribe.models import TranscribeStore, transcribe_stores
|
|
40
40
|
from localstack.services.transcribe.packages import vosk_package
|
|
41
|
+
from localstack.state import StateVisitor
|
|
41
42
|
from localstack.utils.files import new_tmp_file
|
|
42
43
|
from localstack.utils.http import download
|
|
43
44
|
from localstack.utils.run import run
|
|
@@ -101,6 +102,12 @@ _DL_LOCK = threading.Lock()
|
|
|
101
102
|
|
|
102
103
|
|
|
103
104
|
class TranscribeProvider(TranscribeApi):
|
|
105
|
+
def accept_state_visitor(self, visitor: StateVisitor) -> None:
|
|
106
|
+
from moto.transcribe.models import transcribe_backends
|
|
107
|
+
|
|
108
|
+
visitor.visit(transcribe_backends)
|
|
109
|
+
visitor.visit(transcribe_stores)
|
|
110
|
+
|
|
104
111
|
def get_transcription_job(
|
|
105
112
|
self, context: RequestContext, transcription_job_name: TranscriptionJobName, **kwargs: Any
|
|
106
113
|
) -> GetTranscriptionJobResponse:
|
|
@@ -261,7 +261,7 @@ def concurrency_update_done(client, function_name, qualifier):
|
|
|
261
261
|
|
|
262
262
|
def get_invoke_init_type(
|
|
263
263
|
client, function_name, qualifier
|
|
264
|
-
) -> Literal["on-demand", "provisioned-concurrency"]:
|
|
264
|
+
) -> Literal["on-demand", "provisioned-concurrency", "lambda-managed-instances"]:
|
|
265
265
|
"""check the environment in the lambda for AWS_LAMBDA_INITIALIZATION_TYPE indicating ondemand/provisioned"""
|
|
266
266
|
invoke_result = client.invoke(FunctionName=function_name, Qualifier=qualifier)
|
|
267
267
|
return json.load(invoke_result["Payload"])
|
localstack/testing/aws/util.py
CHANGED
|
@@ -29,6 +29,7 @@ from localstack.testing.config import (
|
|
|
29
29
|
SECONDARY_TEST_AWS_SECRET_ACCESS_KEY,
|
|
30
30
|
SECONDARY_TEST_AWS_SESSION_TOKEN,
|
|
31
31
|
TEST_AWS_ACCESS_KEY_ID,
|
|
32
|
+
TEST_AWS_ENDPOINT_URL,
|
|
32
33
|
TEST_AWS_REGION_NAME,
|
|
33
34
|
TEST_AWS_SECRET_ACCESS_KEY,
|
|
34
35
|
)
|
|
@@ -240,7 +241,7 @@ def base_aws_client_factory(session: boto3.Session) -> ClientFactory:
|
|
|
240
241
|
|
|
241
242
|
# Prevent this fixture from using the region configured in system config
|
|
242
243
|
config = config.merge(botocore.config.Config(region_name=TEST_AWS_REGION_NAME))
|
|
243
|
-
return ExternalClientFactory(session=session, config=config)
|
|
244
|
+
return ExternalClientFactory(session=session, config=config, endpoint=TEST_AWS_ENDPOINT_URL)
|
|
244
245
|
|
|
245
246
|
|
|
246
247
|
def base_testing_aws_client(client_factory: ClientFactory) -> ServiceLevelClientFactory:
|
localstack/testing/config.py
CHANGED
|
@@ -9,6 +9,7 @@ TEST_AWS_ACCOUNT_ID = os.getenv("TEST_AWS_ACCOUNT_ID") or DEFAULT_AWS_ACCOUNT_ID
|
|
|
9
9
|
TEST_AWS_ACCESS_KEY_ID = os.getenv("TEST_AWS_ACCESS_KEY_ID") or "test"
|
|
10
10
|
TEST_AWS_SECRET_ACCESS_KEY = os.getenv("TEST_AWS_SECRET_ACCESS_KEY") or "test"
|
|
11
11
|
TEST_AWS_REGION_NAME = os.getenv("TEST_AWS_REGION_NAME") or "us-east-1"
|
|
12
|
+
TEST_AWS_ENDPOINT_URL = os.getenv("TEST_AWS_ENDPOINT_URL")
|
|
12
13
|
|
|
13
14
|
# Secondary test AWS profile - only used for testing against AWS
|
|
14
15
|
SECONDARY_TEST_AWS_PROFILE = os.getenv("SECONDARY_TEST_AWS_PROFILE")
|
|
@@ -61,7 +61,6 @@ if TYPE_CHECKING:
|
|
|
61
61
|
from mypy_boto3_identitystore import IdentityStoreClient
|
|
62
62
|
from mypy_boto3_iot import IoTClient
|
|
63
63
|
from mypy_boto3_iot_data import IoTDataPlaneClient
|
|
64
|
-
from mypy_boto3_iotanalytics import IoTAnalyticsClient
|
|
65
64
|
from mypy_boto3_iotwireless import IoTWirelessClient
|
|
66
65
|
from mypy_boto3_kafka import KafkaClient
|
|
67
66
|
from mypy_boto3_kinesis import KinesisClient
|
|
@@ -72,7 +71,6 @@ if TYPE_CHECKING:
|
|
|
72
71
|
from mypy_boto3_logs import CloudWatchLogsClient
|
|
73
72
|
from mypy_boto3_managedblockchain import ManagedBlockchainClient
|
|
74
73
|
from mypy_boto3_mediaconvert import MediaConvertClient
|
|
75
|
-
from mypy_boto3_mediastore import MediaStoreClient
|
|
76
74
|
from mypy_boto3_mq import MQClient
|
|
77
75
|
from mypy_boto3_mwaa import MWAAClient
|
|
78
76
|
from mypy_boto3_neptune import NeptuneClient
|
|
@@ -184,7 +182,6 @@ class TypedServiceClientFactory(abc.ABC):
|
|
|
184
182
|
identitystore: Union["IdentityStoreClient", "MetadataRequestInjector[IdentityStoreClient]"]
|
|
185
183
|
iot: Union["IoTClient", "MetadataRequestInjector[IoTClient]"]
|
|
186
184
|
iot_data: Union["IoTDataPlaneClient", "MetadataRequestInjector[IoTDataPlaneClient]"]
|
|
187
|
-
iotanalytics: Union["IoTAnalyticsClient", "MetadataRequestInjector[IoTAnalyticsClient]"]
|
|
188
185
|
iotwireless: Union["IoTWirelessClient", "MetadataRequestInjector[IoTWirelessClient]"]
|
|
189
186
|
kafka: Union["KafkaClient", "MetadataRequestInjector[KafkaClient]"]
|
|
190
187
|
kinesis: Union["KinesisClient", "MetadataRequestInjector[KinesisClient]"]
|
|
@@ -199,7 +196,6 @@ class TypedServiceClientFactory(abc.ABC):
|
|
|
199
196
|
"ManagedBlockchainClient", "MetadataRequestInjector[ManagedBlockchainClient]"
|
|
200
197
|
]
|
|
201
198
|
mediaconvert: Union["MediaConvertClient", "MetadataRequestInjector[MediaConvertClient]"]
|
|
202
|
-
mediastore: Union["MediaStoreClient", "MetadataRequestInjector[MediaStoreClient]"]
|
|
203
199
|
mq: Union["MQClient", "MetadataRequestInjector[MQClient]"]
|
|
204
200
|
mwaa: Union["MWAAClient", "MetadataRequestInjector[MWAAClient]"]
|
|
205
201
|
neptune: Union["NeptuneClient", "MetadataRequestInjector[NeptuneClient]"]
|
|
@@ -277,6 +273,8 @@ class ServicePrincipal(str):
|
|
|
277
273
|
appsync = "appsync"
|
|
278
274
|
cloudformation = "cloudformation"
|
|
279
275
|
dms = "dms"
|
|
276
|
+
ecs = "ecs"
|
|
277
|
+
ecs_tasks = "ecs-tasks"
|
|
280
278
|
edgelambda = "edgelambda"
|
|
281
279
|
elasticloadbalancing = "elasticloadbalancing"
|
|
282
280
|
events = "events"
|
localstack/utils/bootstrap.py
CHANGED
|
@@ -34,8 +34,8 @@ from localstack.utils.container_utils.container_client import (
|
|
|
34
34
|
NoSuchImage,
|
|
35
35
|
NoSuchNetwork,
|
|
36
36
|
PortMappings,
|
|
37
|
-
VolumeDirMount,
|
|
38
37
|
VolumeMappings,
|
|
38
|
+
VolumeMappingSpecification,
|
|
39
39
|
)
|
|
40
40
|
from localstack.utils.container_utils.docker_cmd_client import CmdDockerClient
|
|
41
41
|
from localstack.utils.docker_utils import DOCKER_CLIENT
|
|
@@ -666,7 +666,7 @@ class ContainerConfigurators:
|
|
|
666
666
|
return _cfg
|
|
667
667
|
|
|
668
668
|
@staticmethod
|
|
669
|
-
def volume(volume:
|
|
669
|
+
def volume(volume: VolumeMappingSpecification):
|
|
670
670
|
def _cfg(cfg: ContainerConfiguration):
|
|
671
671
|
cfg.volumes.add(volume)
|
|
672
672
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from abc import abstractmethod
|
|
3
|
+
from typing import TypeAlias
|
|
3
4
|
|
|
4
5
|
from plux import Plugin
|
|
5
6
|
|
|
@@ -21,10 +22,10 @@ ServiceOperations = set[str]
|
|
|
21
22
|
ProviderName = str
|
|
22
23
|
CfnResourceName = str
|
|
23
24
|
CfnResourceMethodName = str
|
|
24
|
-
AwsServicesSupportStatus = (
|
|
25
|
+
AwsServicesSupportStatus: TypeAlias = (
|
|
25
26
|
AwsServiceSupportAtRuntime | AwsServicesSupportInLatest | AwsServiceOperationsSupportInLatest
|
|
26
27
|
)
|
|
27
|
-
CfnResourceSupportStatus = (
|
|
28
|
+
CfnResourceSupportStatus: TypeAlias = (
|
|
28
29
|
CloudFormationResourcesSupportInLatest | CloudFormationResourcesSupportAtRuntime
|
|
29
30
|
)
|
|
30
31
|
CfnResourceCatalog = dict[LocalstackEmulatorType, dict[CfnResourceName, set[CfnResourceMethodName]]]
|