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.
Files changed (82) hide show
  1. localstack/aws/api/ec2/__init__.py +13 -0
  2. localstack/aws/api/iam/__init__.py +1 -0
  3. localstack/aws/api/lambda_/__init__.py +616 -0
  4. localstack/aws/api/logs/__init__.py +188 -0
  5. localstack/aws/api/opensearch/__init__.py +11 -0
  6. localstack/aws/api/route53/__init__.py +3 -0
  7. localstack/aws/api/s3/__init__.py +2 -0
  8. localstack/aws/api/s3control/__init__.py +19 -0
  9. localstack/aws/api/secretsmanager/__init__.py +9 -0
  10. localstack/aws/connect.py +35 -15
  11. localstack/aws/protocol/parser.py +6 -1
  12. localstack/aws/spec-patches.json +0 -38
  13. localstack/config.py +8 -0
  14. localstack/constants.py +3 -0
  15. localstack/dev/kubernetes/__main__.py +39 -14
  16. localstack/runtime/analytics.py +11 -0
  17. localstack/services/acm/provider.py +13 -1
  18. localstack/services/apigateway/legacy/provider.py +25 -4
  19. localstack/services/cloudformation/engine/v2/change_set_model.py +9 -0
  20. localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +3 -1
  21. localstack/services/cloudformation/engine/v2/change_set_resource_support_checker.py +114 -0
  22. localstack/services/cloudformation/provider.py +26 -1
  23. localstack/services/cloudformation/provider_utils.py +20 -0
  24. localstack/services/cloudformation/resource_provider.py +5 -4
  25. localstack/services/cloudformation/scaffolding/__main__.py +94 -22
  26. localstack/services/cloudformation/v2/provider.py +41 -0
  27. localstack/services/cloudwatch/models.py +10 -2
  28. localstack/services/cloudwatch/provider_v2.py +15 -20
  29. localstack/services/kinesis/packages.py +1 -1
  30. localstack/services/kms/models.py +6 -2
  31. localstack/services/lambda_/analytics.py +11 -2
  32. localstack/services/lambda_/invocation/event_manager.py +15 -11
  33. localstack/services/lambda_/invocation/lambda_models.py +4 -0
  34. localstack/services/lambda_/invocation/lambda_service.py +11 -0
  35. localstack/services/lambda_/provider.py +70 -13
  36. localstack/services/opensearch/packages.py +34 -20
  37. localstack/services/route53/provider.py +7 -0
  38. localstack/services/route53resolver/provider.py +5 -0
  39. localstack/services/s3/constants.py +5 -0
  40. localstack/services/s3/exceptions.py +9 -0
  41. localstack/services/s3/models.py +9 -1
  42. localstack/services/s3/provider.py +25 -30
  43. localstack/services/s3/utils.py +46 -1
  44. localstack/services/s3control/provider.py +6 -0
  45. localstack/services/scheduler/provider.py +4 -2
  46. localstack/services/secretsmanager/provider.py +4 -0
  47. localstack/services/ses/provider.py +4 -0
  48. localstack/services/sns/constants.py +13 -0
  49. localstack/services/sns/provider.py +5 -0
  50. localstack/services/sns/v2/models.py +4 -0
  51. localstack/services/sns/v2/provider.py +145 -0
  52. localstack/services/sqs/constants.py +6 -0
  53. localstack/services/sqs/provider.py +9 -1
  54. localstack/services/sqs/resource_providers/aws_sqs_queue.py +61 -46
  55. localstack/services/ssm/provider.py +6 -0
  56. localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py +193 -107
  57. localstack/services/stepfunctions/backend/execution.py +4 -5
  58. localstack/services/stepfunctions/provider.py +21 -14
  59. localstack/services/sts/provider.py +7 -0
  60. localstack/services/support/provider.py +5 -1
  61. localstack/services/swf/provider.py +5 -1
  62. localstack/services/transcribe/provider.py +7 -0
  63. localstack/testing/aws/lambda_utils.py +1 -1
  64. localstack/testing/aws/util.py +2 -1
  65. localstack/testing/config.py +1 -0
  66. localstack/utils/aws/client_types.py +2 -4
  67. localstack/utils/bootstrap.py +2 -2
  68. localstack/utils/catalog/catalog.py +3 -2
  69. localstack/utils/container_utils/container_client.py +22 -13
  70. localstack/utils/container_utils/docker_cmd_client.py +6 -6
  71. localstack/version.py +2 -2
  72. {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/METADATA +6 -6
  73. {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/RECORD +81 -80
  74. localstack_core-4.12.1.dev25.dist-info/plux.json +1 -0
  75. localstack_core-4.11.2.dev14.dist-info/plux.json +0 -1
  76. {localstack_core-4.11.2.dev14.data → localstack_core-4.12.1.dev25.data}/scripts/localstack +0 -0
  77. {localstack_core-4.11.2.dev14.data → localstack_core-4.12.1.dev25.data}/scripts/localstack-supervisor +0 -0
  78. {localstack_core-4.11.2.dev14.data → localstack_core-4.12.1.dev25.data}/scripts/localstack.bat +0 -0
  79. {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/WHEEL +0 -0
  80. {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/entry_points.txt +0 -0
  81. {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/licenses/LICENSE.txt +0 -0
  82. {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 validate_mock(mock_input: MockInput, definition: Definition, state_name: StateName) -> None:
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 isinstance(test_state, StateTaskService):
74
- field_validation_mode = mock_input.get(
75
- "fieldValidationMode", MockResponseValidationMode.STRICT
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
- mock_result_raw = mock_input.get("result")
78
- if mock_result_raw is None:
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
- try:
81
- mock_result = json.loads(mock_result_raw)
82
- except json.JSONDecodeError:
83
- raise ValidationException("Mocked result must be valid JSON")
84
- if mock_result is None or field_validation_mode == MockResponseValidationMode.NONE:
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
- boto_service_name = test_state._get_boto_service_name()
88
- service_action_name = test_state._get_boto_service_action()
89
- output_shape = test_state._get_boto_operation_model(
90
- boto_service_name=boto_service_name, service_action_name=service_action_name
91
- ).output_shape
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
- # If the operation has no output, there's nothing to validate
94
- if output_shape is None:
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
- def _raise_type_error(expected_type: str, field_name: str) -> None:
98
- raise ValidationException(
99
- f"Mock result schema validation error: Field '{field_name}' must be {expected_type}"
100
- )
101
-
102
- def _validate_value(value: Any, shape: Shape, field_name: str | None = None) -> None:
103
- # Document type accepts any JSON value
104
- if shape.type_name == "document":
105
- return
106
-
107
- if isinstance(shape, StructureShape):
108
- if not isinstance(value, dict):
109
- # this is a defensive check, the mock result is loaded from JSON before, so should always be a dict
110
- raise ValidationException(
111
- f"Mock result must be a valid JSON object, but got '{type(value)}' instead"
112
- )
113
- # Build a mapping from SFN-normalised member keys -> botocore member shapes
114
- members = shape.members
115
- sfn_key_to_member_shape: dict[str, Shape] = {
116
- StateTaskService._to_sfn_cased(member_key): member_shape
117
- for member_key, member_shape in members.items()
118
- }
119
- if field_validation_mode == MockResponseValidationMode.STRICT:
120
- # Ensure required members are present, using SFN-normalised keys
121
- for required_key in shape.required_members:
122
- sfn_required_key = StateTaskService._to_sfn_cased(required_key)
123
- if sfn_required_key not in value:
124
- raise ValidationException(
125
- f"Mock result schema validation error: Required field '{sfn_required_key}' is missing"
126
- )
127
- # Validate present fields (match SFN-normalised keys to member shapes)
128
- for mock_field_name, mock_field_value in value.items():
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[json | None]
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: json | None
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: json | None = None,
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
- if mock_input is not None:
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=request["roleArn"],
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=request["roleArn"],
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
- pass
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
- pass
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"])
@@ -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:
@@ -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"
@@ -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: BindMount | VolumeDirMount):
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]]]