localstack-core 4.10.1.dev42__py3-none-any.whl → 4.11.2.dev14__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 (116) hide show
  1. localstack/aws/api/apigateway/__init__.py +42 -0
  2. localstack/aws/api/cloudformation/__init__.py +161 -0
  3. localstack/aws/api/ec2/__init__.py +1165 -12
  4. localstack/aws/api/iam/__init__.py +227 -0
  5. localstack/aws/api/kms/__init__.py +1 -0
  6. localstack/aws/api/lambda_/__init__.py +418 -66
  7. localstack/aws/api/logs/__init__.py +312 -0
  8. localstack/aws/api/opensearch/__init__.py +89 -0
  9. localstack/aws/api/redshift/__init__.py +69 -0
  10. localstack/aws/api/resourcegroupstaggingapi/__init__.py +36 -0
  11. localstack/aws/api/route53/__init__.py +42 -0
  12. localstack/aws/api/route53resolver/__init__.py +1 -0
  13. localstack/aws/api/s3/__init__.py +62 -0
  14. localstack/aws/api/secretsmanager/__init__.py +28 -23
  15. localstack/aws/api/stepfunctions/__init__.py +52 -10
  16. localstack/aws/api/sts/__init__.py +52 -0
  17. localstack/aws/handlers/logging.py +8 -4
  18. localstack/aws/handlers/service.py +11 -2
  19. localstack/aws/protocol/serializer.py +1 -1
  20. localstack/deprecations.py +0 -6
  21. localstack/services/acm/provider.py +4 -0
  22. localstack/services/apigateway/legacy/provider.py +28 -15
  23. localstack/services/cloudformation/engine/template_preparer.py +6 -2
  24. localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +12 -0
  25. localstack/services/cloudwatch/provider.py +10 -3
  26. localstack/services/cloudwatch/provider_v2.py +6 -3
  27. localstack/services/configservice/provider.py +5 -1
  28. localstack/services/dynamodb/provider.py +1 -0
  29. localstack/services/dynamodb/v2/provider.py +1 -0
  30. localstack/services/dynamodbstreams/provider.py +6 -0
  31. localstack/services/dynamodbstreams/v2/provider.py +6 -0
  32. localstack/services/ec2/provider.py +6 -0
  33. localstack/services/es/provider.py +6 -0
  34. localstack/services/events/provider.py +4 -0
  35. localstack/services/events/v1/provider.py +9 -0
  36. localstack/services/firehose/provider.py +5 -0
  37. localstack/services/iam/provider.py +4 -0
  38. localstack/services/kms/models.py +10 -20
  39. localstack/services/kms/provider.py +4 -0
  40. localstack/services/lambda_/api_utils.py +37 -20
  41. localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +1 -1
  42. localstack/services/lambda_/invocation/assignment.py +4 -1
  43. localstack/services/lambda_/invocation/execution_environment.py +21 -2
  44. localstack/services/lambda_/invocation/lambda_models.py +27 -2
  45. localstack/services/lambda_/invocation/lambda_service.py +51 -3
  46. localstack/services/lambda_/invocation/models.py +9 -1
  47. localstack/services/lambda_/invocation/version_manager.py +18 -3
  48. localstack/services/lambda_/provider.py +239 -95
  49. localstack/services/lambda_/resource_providers/aws_lambda_function.py +33 -1
  50. localstack/services/lambda_/runtimes.py +3 -1
  51. localstack/services/logs/provider.py +9 -0
  52. localstack/services/opensearch/provider.py +53 -3
  53. localstack/services/resource_groups/provider.py +5 -1
  54. localstack/services/resourcegroupstaggingapi/provider.py +6 -1
  55. localstack/services/s3/provider.py +28 -15
  56. localstack/services/s3/utils.py +35 -14
  57. localstack/services/s3control/provider.py +101 -2
  58. localstack/services/s3control/validation.py +50 -0
  59. localstack/services/sns/constants.py +3 -1
  60. localstack/services/sns/publisher.py +15 -6
  61. localstack/services/sns/v2/models.py +6 -0
  62. localstack/services/sns/v2/provider.py +650 -19
  63. localstack/services/sns/v2/utils.py +12 -0
  64. localstack/services/stepfunctions/asl/component/common/path/result_path.py +1 -1
  65. localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py +0 -1
  66. localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +0 -1
  67. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py +8 -8
  68. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/{mock_eval_utils.py → local_mock_eval_utils.py} +13 -9
  69. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py +6 -6
  70. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py +1 -1
  71. localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +4 -0
  72. localstack/services/stepfunctions/asl/component/test_state/state/base_mock.py +118 -0
  73. localstack/services/stepfunctions/asl/component/test_state/state/common.py +82 -0
  74. localstack/services/stepfunctions/asl/component/test_state/state/execution.py +139 -0
  75. localstack/services/stepfunctions/asl/component/test_state/state/map.py +77 -0
  76. localstack/services/stepfunctions/asl/component/test_state/state/task.py +44 -0
  77. localstack/services/stepfunctions/asl/eval/environment.py +30 -22
  78. localstack/services/stepfunctions/asl/eval/states.py +1 -1
  79. localstack/services/stepfunctions/asl/eval/test_state/environment.py +49 -9
  80. localstack/services/stepfunctions/asl/eval/test_state/program_state.py +22 -0
  81. localstack/services/stepfunctions/asl/jsonata/jsonata.py +5 -1
  82. localstack/services/stepfunctions/asl/parse/preprocessor.py +67 -24
  83. localstack/services/stepfunctions/asl/parse/test_state/asl_parser.py +5 -4
  84. localstack/services/stepfunctions/asl/parse/test_state/preprocessor.py +222 -31
  85. localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py +170 -22
  86. localstack/services/stepfunctions/backend/execution.py +6 -6
  87. localstack/services/stepfunctions/backend/execution_worker.py +5 -5
  88. localstack/services/stepfunctions/backend/test_state/execution.py +36 -0
  89. localstack/services/stepfunctions/backend/test_state/execution_worker.py +33 -1
  90. localstack/services/stepfunctions/backend/test_state/test_state_mock.py +127 -0
  91. localstack/services/stepfunctions/local_mocking/__init__.py +9 -0
  92. localstack/services/stepfunctions/{mocking → local_mocking}/mock_config.py +24 -17
  93. localstack/services/stepfunctions/provider.py +78 -27
  94. localstack/services/stepfunctions/test_state/mock_config.py +47 -0
  95. localstack/testing/pytest/fixtures.py +28 -0
  96. localstack/testing/snapshots/transformer_utility.py +5 -0
  97. localstack/utils/analytics/publisher.py +37 -155
  98. localstack/utils/analytics/service_request_aggregator.py +6 -4
  99. localstack/utils/aws/arns.py +7 -0
  100. localstack/utils/batching.py +258 -0
  101. localstack/utils/collections.py +23 -11
  102. localstack/version.py +2 -2
  103. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.11.2.dev14.dist-info}/METADATA +5 -5
  104. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.11.2.dev14.dist-info}/RECORD +113 -105
  105. localstack_core-4.11.2.dev14.dist-info/plux.json +1 -0
  106. localstack/services/stepfunctions/mocking/__init__.py +0 -0
  107. localstack/utils/batch_policy.py +0 -124
  108. localstack_core-4.10.1.dev42.dist-info/plux.json +0 -1
  109. /localstack/services/stepfunctions/{mocking → local_mocking}/mock_config_file.py +0 -0
  110. {localstack_core-4.10.1.dev42.data → localstack_core-4.11.2.dev14.data}/scripts/localstack +0 -0
  111. {localstack_core-4.10.1.dev42.data → localstack_core-4.11.2.dev14.data}/scripts/localstack-supervisor +0 -0
  112. {localstack_core-4.10.1.dev42.data → localstack_core-4.11.2.dev14.data}/scripts/localstack.bat +0 -0
  113. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.11.2.dev14.dist-info}/WHEEL +0 -0
  114. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.11.2.dev14.dist-info}/entry_points.txt +0 -0
  115. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.11.2.dev14.dist-info}/licenses/LICENSE.txt +0 -0
  116. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.11.2.dev14.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,7 @@ Definition = str
13
13
  Enabled = bool
14
14
  ErrorMessage = str
15
15
  EvaluationFailureLocation = str
16
+ ExceptionHandlerIndex = int
16
17
  HTTPBody = str
17
18
  HTTPHeaders = str
18
19
  HTTPMethod = str
@@ -22,10 +23,14 @@ HTTPStatusMessage = str
22
23
  Identity = str
23
24
  IncludeExecutionData = bool
24
25
  IncludeExecutionDataGetExecutionHistory = bool
26
+ InspectionMaxConcurrency = int
27
+ InspectionToleratedFailureCount = int
28
+ InspectionToleratedFailurePercentage = float
25
29
  KmsDataKeyReusePeriodSeconds = int
26
30
  KmsKeyId = str
27
31
  ListExecutionsPageToken = str
28
32
  LongArn = str
33
+ MapIterationFailureCount = int
29
34
  MapRunLabel = str
30
35
  MaxConcurrency = int
31
36
  Name = str
@@ -33,6 +38,8 @@ PageSize = int
33
38
  PageToken = str
34
39
  Publish = bool
35
40
  RedriveCount = int
41
+ RetrierRetryCount = int
42
+ RetryBackoffIntervalSeconds = int
36
43
  RevealSecrets = bool
37
44
  ReverseOrder = bool
38
45
  RevisionId = str
@@ -44,6 +51,7 @@ StateName = str
44
51
  TagKey = str
45
52
  TagValue = str
46
53
  TaskToken = str
54
+ TestStateStateName = str
47
55
  ToleratedFailurePercentage = float
48
56
  TraceHeader = str
49
57
  URL = str
@@ -184,6 +192,12 @@ class MapRunStatus(StrEnum):
184
192
  ABORTED = "ABORTED"
185
193
 
186
194
 
195
+ class MockResponseValidationMode(StrEnum):
196
+ STRICT = "STRICT"
197
+ PRESENT = "PRESENT"
198
+ NONE = "NONE"
199
+
200
+
187
201
  class StateMachineStatus(StrEnum):
188
202
  ACTIVE = "ACTIVE"
189
203
  DELETING = "DELETING"
@@ -1024,6 +1038,12 @@ class GetExecutionHistoryOutput(TypedDict, total=False):
1024
1038
  nextToken: PageToken | None
1025
1039
 
1026
1040
 
1041
+ class InspectionErrorDetails(TypedDict, total=False):
1042
+ catchIndex: ExceptionHandlerIndex | None
1043
+ retryIndex: ExceptionHandlerIndex | None
1044
+ retryBackoffIntervalSeconds: RetryBackoffIntervalSeconds | None
1045
+
1046
+
1027
1047
  class InspectionDataResponse(TypedDict, total=False):
1028
1048
  protocol: HTTPProtocol | None
1029
1049
  statusCode: HTTPStatusCode | None
@@ -1051,6 +1071,14 @@ class InspectionData(TypedDict, total=False):
1051
1071
  request: InspectionDataRequest | None
1052
1072
  response: InspectionDataResponse | None
1053
1073
  variables: SensitiveData | None
1074
+ errorDetails: InspectionErrorDetails | None
1075
+ afterItemsPath: SensitiveData | None
1076
+ afterItemSelector: SensitiveData | None
1077
+ afterItemBatcher: SensitiveData | None
1078
+ afterItemsPointer: SensitiveData | None
1079
+ toleratedFailureCount: InspectionToleratedFailureCount | None
1080
+ toleratedFailurePercentage: InspectionToleratedFailurePercentage | None
1081
+ maxConcurrency: InspectionMaxConcurrency | None
1054
1082
 
1055
1083
 
1056
1084
  class ListActivitiesInput(ServiceRequest):
@@ -1165,6 +1193,17 @@ class ListTagsForResourceOutput(TypedDict, total=False):
1165
1193
  tags: TagList | None
1166
1194
 
1167
1195
 
1196
+ class MockErrorOutput(TypedDict, total=False):
1197
+ error: SensitiveError | None
1198
+ cause: SensitiveCause | None
1199
+
1200
+
1201
+ class MockInput(TypedDict, total=False):
1202
+ result: SensitiveData | None
1203
+ errorOutput: MockErrorOutput | None
1204
+ fieldValidationMode: MockResponseValidationMode | None
1205
+
1206
+
1168
1207
  class PublishStateMachineVersionInput(ServiceRequest):
1169
1208
  stateMachineArn: Arn
1170
1209
  revisionId: RevisionId | None
@@ -1271,6 +1310,13 @@ class TagResourceOutput(TypedDict, total=False):
1271
1310
  pass
1272
1311
 
1273
1312
 
1313
+ class TestStateConfiguration(TypedDict, total=False):
1314
+ retrierRetryCount: RetrierRetryCount | None
1315
+ errorCausedByState: TestStateStateName | None
1316
+ mapIterationFailureCount: MapIterationFailureCount | None
1317
+ mapItemReaderData: SensitiveData | None
1318
+
1319
+
1274
1320
  class TestStateInput(ServiceRequest):
1275
1321
  definition: Definition
1276
1322
  roleArn: Arn | None
@@ -1278,6 +1324,10 @@ class TestStateInput(ServiceRequest):
1278
1324
  inspectionLevel: InspectionLevel | None
1279
1325
  revealSecrets: RevealSecrets | None
1280
1326
  variables: SensitiveData | None
1327
+ stateName: TestStateStateName | None
1328
+ mock: MockInput | None
1329
+ context: SensitiveData | None
1330
+ stateConfiguration: TestStateConfiguration | None
1281
1331
 
1282
1332
 
1283
1333
  class TestStateOutput(TypedDict, total=False):
@@ -1641,17 +1691,9 @@ class StepfunctionsApi:
1641
1691
  ) -> TagResourceOutput:
1642
1692
  raise NotImplementedError
1643
1693
 
1644
- @handler("TestState")
1694
+ @handler("TestState", expand=False)
1645
1695
  def test_state(
1646
- self,
1647
- context: RequestContext,
1648
- definition: Definition,
1649
- role_arn: Arn | None = None,
1650
- input: SensitiveData | None = None,
1651
- inspection_level: InspectionLevel | None = None,
1652
- reveal_secrets: RevealSecrets | None = None,
1653
- variables: SensitiveData | None = None,
1654
- **kwargs,
1696
+ self, context: RequestContext, request: TestStateInput, **kwargs
1655
1697
  ) -> TestStateOutput:
1656
1698
  raise NotImplementedError
1657
1699
 
@@ -29,13 +29,17 @@ idpCommunicationErrorMessage = str
29
29
  idpRejectedClaimMessage = str
30
30
  invalidAuthorizationMessage = str
31
31
  invalidIdentityTokenMessage = str
32
+ jwtAlgorithmType = str
33
+ jwtPayloadSizeExceededException = str
32
34
  malformedPolicyDocumentMessage = str
33
35
  nonNegativeIntegerType = int
36
+ outboundWebIdentityFederationDisabledException = str
34
37
  packedPolicyTooLargeMessage = str
35
38
  regionDisabledMessage = str
36
39
  roleDurationSecondsType = int
37
40
  roleSessionNameType = str
38
41
  serialNumberType = str
42
+ sessionDurationEscalationException = str
39
43
  sessionPolicyDocumentType = str
40
44
  sourceIdentityType = str
41
45
  tagKeyType = str
@@ -48,6 +52,9 @@ urlType = str
48
52
  userIdType = str
49
53
  userNameType = str
50
54
  webIdentitySubjectType = str
55
+ webIdentityTokenAudienceStringType = str
56
+ webIdentityTokenDurationSecondsType = int
57
+ webIdentityTokenType = str
51
58
 
52
59
 
53
60
  class ExpiredTokenException(ServiceException):
@@ -86,12 +93,24 @@ class InvalidIdentityTokenException(ServiceException):
86
93
  status_code: int = 400
87
94
 
88
95
 
96
+ class JWTPayloadSizeExceededException(ServiceException):
97
+ code: str = "JWTPayloadSizeExceededException"
98
+ sender_fault: bool = True
99
+ status_code: int = 400
100
+
101
+
89
102
  class MalformedPolicyDocumentException(ServiceException):
90
103
  code: str = "MalformedPolicyDocument"
91
104
  sender_fault: bool = True
92
105
  status_code: int = 400
93
106
 
94
107
 
108
+ class OutboundWebIdentityFederationDisabledException(ServiceException):
109
+ code: str = "OutboundWebIdentityFederationDisabledException"
110
+ sender_fault: bool = True
111
+ status_code: int = 403
112
+
113
+
95
114
  class PackedPolicyTooLargeException(ServiceException):
96
115
  code: str = "PackedPolicyTooLarge"
97
116
  sender_fault: bool = True
@@ -104,6 +123,12 @@ class RegionDisabledException(ServiceException):
104
123
  status_code: int = 403
105
124
 
106
125
 
126
+ class SessionDurationEscalationException(ServiceException):
127
+ code: str = "SessionDurationEscalationException"
128
+ sender_fault: bool = True
129
+ status_code: int = 403
130
+
131
+
107
132
  class ProvidedContext(TypedDict, total=False):
108
133
  ProviderArn: arnType | None
109
134
  ContextAssertion: contextAssertionType | None
@@ -282,6 +307,21 @@ class GetSessionTokenResponse(TypedDict, total=False):
282
307
  Credentials: Credentials | None
283
308
 
284
309
 
310
+ webIdentityTokenAudienceListType = list[webIdentityTokenAudienceStringType]
311
+
312
+
313
+ class GetWebIdentityTokenRequest(ServiceRequest):
314
+ Audience: webIdentityTokenAudienceListType
315
+ DurationSeconds: webIdentityTokenDurationSecondsType | None
316
+ SigningAlgorithm: jwtAlgorithmType
317
+ Tags: tagListType | None
318
+
319
+
320
+ class GetWebIdentityTokenResponse(TypedDict, total=False):
321
+ WebIdentityToken: webIdentityTokenType | None
322
+ Expiration: dateType | None
323
+
324
+
285
325
  class StsApi:
286
326
  service: str = "sts"
287
327
  version: str = "2011-06-15"
@@ -391,3 +431,15 @@ class StsApi:
391
431
  **kwargs,
392
432
  ) -> GetSessionTokenResponse:
393
433
  raise NotImplementedError
434
+
435
+ @handler("GetWebIdentityToken")
436
+ def get_web_identity_token(
437
+ self,
438
+ context: RequestContext,
439
+ audience: webIdentityTokenAudienceListType,
440
+ signing_algorithm: jwtAlgorithmType,
441
+ duration_seconds: webIdentityTokenDurationSecondsType | None = None,
442
+ tags: tagListType | None = None,
443
+ **kwargs,
444
+ ) -> GetWebIdentityTokenResponse:
445
+ raise NotImplementedError
@@ -1,7 +1,6 @@
1
1
  """Handlers for logging."""
2
2
 
3
3
  import logging
4
- import types
5
4
  from functools import cached_property
6
5
 
7
6
  from localstack.aws.api import RequestContext, ServiceException
@@ -25,10 +24,14 @@ class ExceptionLogger(ExceptionHandler):
25
24
  try:
26
25
  import moto.core.exceptions
27
26
 
28
- self._moto_service_exception = moto.core.exceptions.ServiceException
27
+ self._skip_exceptions(
28
+ ServiceException,
29
+ moto.core.exceptions.ServiceException,
30
+ moto.core.exceptions.RESTError,
31
+ )
29
32
  except (ModuleNotFoundError, AttributeError):
30
33
  # Moto may not be available in stripped-down versions of LocalStack, like LocalStack S3 image.
31
- self._moto_service_exception = types.EllipsisType
34
+ self._skip_exceptions = (ServiceException,)
32
35
 
33
36
  def __call__(
34
37
  self,
@@ -37,11 +40,12 @@ class ExceptionLogger(ExceptionHandler):
37
40
  context: RequestContext,
38
41
  response: Response,
39
42
  ):
40
- if isinstance(exception, (ServiceException, self._moto_service_exception)):
43
+ if isinstance(exception, self._skip_exceptions):
41
44
  # We do not want to log an error/stacktrace if the handler is working as expected, but chooses to throw
42
45
  # a service exception. It may also throw a Moto ServiceException, which should not be logged either
43
46
  # because ServiceExceptionHandler understands it.
44
47
  return
48
+
45
49
  if self.logger.isEnabledFor(level=logging.DEBUG):
46
50
  self.logger.exception("exception during call chain", exc_info=exception)
47
51
  else:
@@ -158,14 +158,17 @@ class ServiceExceptionSerializer(ExceptionHandler):
158
158
  def __init__(self):
159
159
  self.handle_internal_failures = True
160
160
 
161
+ self._moto_service_exception = types.EllipsisType
162
+ self._moto_rest_error = types.EllipsisType
163
+
161
164
  try:
162
165
  import moto.core.exceptions
163
166
 
164
167
  self._moto_service_exception = moto.core.exceptions.ServiceException
168
+ self._moto_rest_error = moto.core.exceptions.RESTError
165
169
  except (ModuleNotFoundError, AttributeError) as exc:
166
170
  # Moto may not be available in stripped-down versions of LocalStack, like LocalStack S3 image.
167
- LOG.debug("Unable to set up Moto ServiceException translation: %s", exc)
168
- self._moto_service_exception = types.EllipsisType
171
+ LOG.debug("Unable to set up Moto exception translation: %s", exc)
169
172
 
170
173
  def __call__(
171
174
  self,
@@ -200,6 +203,12 @@ class ServiceExceptionSerializer(ExceptionHandler):
200
203
  code=exception.code,
201
204
  message=exception.message,
202
205
  )
206
+ elif isinstance(exception, self._moto_rest_error):
207
+ # Some Moto exceptions (like ones raised by EC2) are of type RESTError.
208
+ error = CommonServiceException(
209
+ code=exception.error_type,
210
+ message=exception.message,
211
+ )
203
212
 
204
213
  elif not isinstance(exception, ServiceException):
205
214
  if not self.handle_internal_failures:
@@ -2192,7 +2192,7 @@ class S3ResponseSerializer(RestXMLResponseSerializer):
2192
2192
 
2193
2193
  def _prepare_additional_traits_in_xml(self, root: ETree.Element | None, request_id: str):
2194
2194
  # some tools (Serverless) require a newline after the "<?xml ...>\n" preamble line, e.g., for LocationConstraint
2195
- if root and not root.tail:
2195
+ if root is not None and not root.tail:
2196
2196
  root.tail = "\n"
2197
2197
 
2198
2198
  root.attrib["xmlns"] = self.XML_NAMESPACE
@@ -35,12 +35,6 @@ class EnvVarDeprecation:
35
35
  # Please make sure this is in-sync with https://docs.localstack.cloud/references/configuration/
36
36
  #
37
37
  DEPRECATIONS = [
38
- # Since 0.11.3 - HTTP / HTTPS multiplexing
39
- EnvVarDeprecation(
40
- "USE_SSL",
41
- "0.11.3",
42
- "Each endpoint now supports multiplexing HTTP/HTTPS traffic over the same port. Please remove this environment variable.", # noqa
43
- ),
44
38
  # Since 0.12.8 - PORT_UI was removed
45
39
  EnvVarDeprecation(
46
40
  "PORT_WEB_UI",
@@ -10,6 +10,7 @@ from localstack.aws.api.acm import (
10
10
  RequestCertificateResponse,
11
11
  )
12
12
  from localstack.services import moto
13
+ from localstack.state import StateVisitor
13
14
  from localstack.utils.patch import patch
14
15
 
15
16
  # reduce the validation wait time from 60 (default) to 10 seconds
@@ -100,6 +101,9 @@ def describe(describe_orig, self):
100
101
 
101
102
 
102
103
  class AcmProvider(AcmApi):
104
+ def accept_state_visitor(self, visitor: StateVisitor):
105
+ visitor.visit(acm_models.acm_backends)
106
+
103
107
  @handler("RequestCertificate", expand=False)
104
108
  def request_certificate(
105
109
  self,
@@ -42,6 +42,7 @@ from localstack.aws.api.apigateway import (
42
42
  DomainName,
43
43
  DomainNames,
44
44
  DomainNameStatus,
45
+ EndpointAccessMode,
45
46
  EndpointConfiguration,
46
47
  EndpointType,
47
48
  ExportResponse,
@@ -117,7 +118,11 @@ from localstack.services.apigateway.helpers import (
117
118
  from localstack.services.apigateway.legacy.helpers import multi_value_dict_for_list
118
119
  from localstack.services.apigateway.legacy.invocations import invoke_rest_api_from_request
119
120
  from localstack.services.apigateway.legacy.router_asf import ApigatewayRouter, to_invocation_context
120
- from localstack.services.apigateway.models import ApiGatewayStore, RestApiContainer
121
+ from localstack.services.apigateway.models import (
122
+ ApiGatewayStore,
123
+ RestApiContainer,
124
+ apigateway_stores,
125
+ )
121
126
  from localstack.services.apigateway.next_gen.execute_api.router import (
122
127
  ApiGatewayRouter as ApiGatewayRouterNextGen,
123
128
  )
@@ -125,6 +130,7 @@ from localstack.services.apigateway.patches import apply_patches
125
130
  from localstack.services.edge import ROUTER
126
131
  from localstack.services.moto import call_moto, call_moto_with_request
127
132
  from localstack.services.plugins import ServiceLifecycleHook
133
+ from localstack.state import StateVisitor
128
134
  from localstack.utils.aws.arns import InvalidArnException, get_partition, parse_arn
129
135
  from localstack.utils.collections import (
130
136
  DelSafeDict,
@@ -191,6 +197,12 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
191
197
  apply_patches()
192
198
  self.router.register_routes()
193
199
 
200
+ def accept_state_visitor(self, visitor: StateVisitor):
201
+ from moto.apigateway import apigateway_backends
202
+
203
+ visitor.visit(apigateway_backends)
204
+ visitor.visit(apigateway_stores)
205
+
194
206
  @handler("TestInvokeMethod", expand=False)
195
207
  def test_invoke_method(
196
208
  self, context: RequestContext, request: TestInvokeMethodRequest
@@ -511,20 +523,21 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
511
523
  self,
512
524
  context: RequestContext,
513
525
  domain_name: String,
514
- certificate_name: String = None,
515
- certificate_body: String = None,
516
- certificate_private_key: String = None,
517
- certificate_chain: String = None,
518
- certificate_arn: String = None,
519
- regional_certificate_name: String = None,
520
- regional_certificate_arn: String = None,
521
- endpoint_configuration: EndpointConfiguration = None,
522
- tags: MapOfStringToString = None,
523
- security_policy: SecurityPolicy = None,
524
- mutual_tls_authentication: MutualTlsAuthenticationInput = None,
525
- ownership_verification_certificate_arn: String = None,
526
- policy: String = None,
527
- routing_mode: RoutingMode = None,
526
+ certificate_name: String | None = None,
527
+ certificate_body: String | None = None,
528
+ certificate_private_key: String | None = None,
529
+ certificate_chain: String | None = None,
530
+ certificate_arn: String | None = None,
531
+ regional_certificate_name: String | None = None,
532
+ regional_certificate_arn: String | None = None,
533
+ endpoint_configuration: EndpointConfiguration | None = None,
534
+ tags: MapOfStringToString | None = None,
535
+ security_policy: SecurityPolicy | None = None,
536
+ endpoint_access_mode: EndpointAccessMode | None = None,
537
+ mutual_tls_authentication: MutualTlsAuthenticationInput | None = None,
538
+ ownership_verification_certificate_arn: String | None = None,
539
+ policy: String | None = None,
540
+ routing_mode: RoutingMode | None = None,
528
541
  **kwargs,
529
542
  ) -> DomainName:
530
543
  if not domain_name:
@@ -6,6 +6,7 @@ from localstack.services.cloudformation.engine.transformers import (
6
6
  apply_global_transformations,
7
7
  apply_intrinsic_transformations,
8
8
  )
9
+ from localstack.services.cloudformation.engine.validations import ValidationError
9
10
  from localstack.utils.json import clone_safe
10
11
 
11
12
  LOG = logging.getLogger(__name__)
@@ -17,9 +18,12 @@ def parse_template(template: str) -> dict:
17
18
  except Exception:
18
19
  try:
19
20
  return clone_safe(yaml_parser.parse_yaml(template))
20
- except Exception as e:
21
- LOG.debug("Unable to parse CloudFormation template (%s): %s", e, template)
21
+ except ValidationError:
22
+ # The error is handled in the yaml parsing helper
22
23
  raise
24
+ except Exception:
25
+ # TODO: present the user with a better error message including error location
26
+ raise ValidationError("Template format error: YAML not well-formed.")
23
27
 
24
28
 
25
29
  def template_to_json(template: str) -> str:
@@ -677,6 +677,13 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
677
677
  node_condition = self._get_node_condition_if_exists(
678
678
  condition_name=condition_delta.before
679
679
  )
680
+ if is_nothing(node_condition):
681
+ # TODO: I don't think this is a possible state since for us to be evaluating the before state,
682
+ # we must have successfully deployed the stack and as such this case was not reached before
683
+ raise ValidationError(
684
+ f"Template error: unresolved condition dependency {condition_delta.before} in Fn::If"
685
+ )
686
+
680
687
  condition_value = self.visit(node_condition).before
681
688
  if condition_value:
682
689
  arg_delta = self.visit(node_intrinsic_function.arguments.array[1])
@@ -688,6 +695,11 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
688
695
  node_condition = self._get_node_condition_if_exists(
689
696
  condition_name=condition_delta.after
690
697
  )
698
+ if is_nothing(node_condition):
699
+ raise ValidationError(
700
+ f"Template error: unresolved condition dependency {condition_delta.after} in Fn::If"
701
+ )
702
+
691
703
  condition_value = self.visit(node_condition).after
692
704
  if condition_value:
693
705
  arg_delta = self.visit(node_intrinsic_function.arguments.array[1])
@@ -35,6 +35,7 @@ from localstack.services import moto
35
35
  from localstack.services.cloudwatch.alarm_scheduler import AlarmScheduler
36
36
  from localstack.services.edge import ROUTER
37
37
  from localstack.services.plugins import SERVICE_PLUGINS, ServiceLifecycleHook
38
+ from localstack.state import StateVisitor
38
39
  from localstack.utils.aws import arns
39
40
  from localstack.utils.aws.arns import extract_account_id_from_arn, lambda_function_name
40
41
  from localstack.utils.aws.request_context import (
@@ -306,8 +307,13 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook):
306
307
  self.tags = TaggingService()
307
308
  self.alarm_scheduler = None
308
309
 
310
+ def accept_state_visitor(self, visitor: StateVisitor):
311
+ visitor.visit(cloudwatch_backends)
312
+
309
313
  def on_after_init(self):
310
314
  ROUTER.add(PATH_GET_RAW_METRICS, self.get_raw_metrics)
315
+
316
+ def on_before_start(self):
311
317
  self.start_alarm_scheduler()
312
318
 
313
319
  def on_before_state_reset(self):
@@ -337,9 +343,10 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook):
337
343
  self.alarm_scheduler = AlarmScheduler()
338
344
 
339
345
  def shutdown_alarm_scheduler(self):
340
- LOG.debug("stopping cloudwatch scheduler")
341
- self.alarm_scheduler.shutdown_scheduler()
342
- self.alarm_scheduler = None
346
+ if self.alarm_scheduler:
347
+ LOG.debug("stopping cloudwatch scheduler")
348
+ self.alarm_scheduler.shutdown_scheduler()
349
+ self.alarm_scheduler = None
343
350
 
344
351
  def delete_alarms(self, context: RequestContext, alarm_names: AlarmNames, **kwargs) -> None:
345
352
  moto.call_moto(context)
@@ -161,6 +161,8 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook):
161
161
 
162
162
  def on_after_init(self):
163
163
  ROUTER.add(PATH_GET_RAW_METRICS, self.get_raw_metrics)
164
+
165
+ def on_before_start(self):
164
166
  self.start_alarm_scheduler()
165
167
 
166
168
  def on_before_state_reset(self):
@@ -192,9 +194,10 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook):
192
194
  self.alarm_scheduler = AlarmScheduler()
193
195
 
194
196
  def shutdown_alarm_scheduler(self):
195
- LOG.debug("stopping cloudwatch scheduler")
196
- self.alarm_scheduler.shutdown_scheduler()
197
- self.alarm_scheduler = None
197
+ if self.alarm_scheduler:
198
+ LOG.debug("stopping cloudwatch scheduler")
199
+ self.alarm_scheduler.shutdown_scheduler()
200
+ self.alarm_scheduler = None
198
201
 
199
202
  def delete_alarms(self, context: RequestContext, alarm_names: AlarmNames, **kwargs) -> None:
200
203
  """
@@ -1,5 +1,9 @@
1
1
  from localstack.aws.api.config import ConfigApi
2
+ from localstack.state import StateVisitor
2
3
 
3
4
 
4
5
  class ConfigProvider(ConfigApi):
5
- pass
6
+ def accept_state_visitor(self, visitor: StateVisitor):
7
+ from moto.config.models import config_backends
8
+
9
+ visitor.visit(config_backends)
@@ -535,6 +535,7 @@ class DynamoDBProvider(DynamodbApi, ServiceLifecycleHook):
535
535
  self.server = self._new_dynamodb_server()
536
536
  self._expired_items_worker = ExpiredItemsWorker()
537
537
  self._router_rules = []
538
+ # TODO: fix _event_forwarder to have lazy instantiation of the ThreadPoolExecutor
538
539
  self._event_forwarder = EventForwarder()
539
540
 
540
541
  def on_before_start(self):
@@ -392,6 +392,7 @@ class DynamoDBProvider(DynamodbApi, ServiceLifecycleHook):
392
392
 
393
393
  def accept_state_visitor(self, visitor: StateVisitor):
394
394
  visitor.visit(dynamodb_stores)
395
+ # FIXME: DDB v2 does not depend on dynamodbstreams_stores as DynamoDB Local holds all the state
395
396
  visitor.visit(dynamodbstreams_stores)
396
397
  visitor.visit(AssetDirectory(self.service, os.path.join(config.dirs.data, self.service)))
397
398
 
@@ -37,6 +37,7 @@ from localstack.services.dynamodbstreams.dynamodbstreams_api import (
37
37
  table_name_from_stream_arn,
38
38
  )
39
39
  from localstack.services.plugins import ServiceLifecycleHook
40
+ from localstack.state import StateVisitor
40
41
  from localstack.utils.collections import select_from_typed_dict
41
42
 
42
43
  LOG = logging.getLogger(__name__)
@@ -57,6 +58,11 @@ class DynamoDBStreamsProvider(DynamodbstreamsApi, ServiceLifecycleHook):
57
58
  def __init__(self):
58
59
  self.shard_to_region = {}
59
60
 
61
+ def accept_state_visitor(self, visitor: StateVisitor):
62
+ from localstack.services.dynamodbstreams.models import dynamodbstreams_stores
63
+
64
+ visitor.visit(dynamodbstreams_stores)
65
+
60
66
  def describe_stream(
61
67
  self,
62
68
  context: RequestContext,
@@ -18,6 +18,7 @@ from localstack.services.dynamodb.utils import modify_ddblocal_arns
18
18
  from localstack.services.dynamodb.v2.provider import DynamoDBProvider, modify_context_region
19
19
  from localstack.services.dynamodbstreams.dynamodbstreams_api import get_original_region
20
20
  from localstack.services.plugins import ServiceLifecycleHook
21
+ from localstack.state import StateVisitor
21
22
  from localstack.utils.aws.arns import parse_arn
22
23
 
23
24
  LOG = logging.getLogger(__name__)
@@ -32,6 +33,11 @@ class DynamoDBStreamsProvider(DynamodbstreamsApi, ServiceLifecycleHook):
32
33
  self.server = DynamodbServer.get()
33
34
  self.shard_to_region = {}
34
35
 
36
+ def accept_state_visitor(self, visitor: StateVisitor):
37
+ # DynamoDB Streams state is entirely dependent on DynamoDB Local state, and does not hold state itself
38
+ # the DynamoDB provider is responsible for the persistence of DDB Streams
39
+ pass
40
+
35
41
  def on_after_init(self):
36
42
  # add response processor specific to ddblocal
37
43
  handlers.modify_service_response.append(self.service, modify_ddblocal_arns)
@@ -94,6 +94,7 @@ from localstack.services.ec2.models import get_ec2_backend
94
94
  from localstack.services.ec2.patches import apply_patches
95
95
  from localstack.services.moto import call_moto, call_moto_with_request
96
96
  from localstack.services.plugins import ServiceLifecycleHook
97
+ from localstack.state import StateVisitor
97
98
  from localstack.utils.patch import patch
98
99
  from localstack.utils.strings import first_char_to_upper, long_uid, short_uid
99
100
 
@@ -107,6 +108,11 @@ class Ec2Provider(Ec2Api, ABC, ServiceLifecycleHook):
107
108
  def on_after_init(self):
108
109
  apply_patches()
109
110
 
111
+ def accept_state_visitor(self, visitor: StateVisitor):
112
+ from moto.ec2.models import ec2_backends
113
+
114
+ visitor.visit(ec2_backends)
115
+
110
116
  @handler("DescribeAvailabilityZones", expand=False)
111
117
  def describe_availability_zones(
112
118
  self,
@@ -68,6 +68,7 @@ from localstack.aws.api.opensearch import (
68
68
  )
69
69
  from localstack.aws.connect import connect_to
70
70
  from localstack.services.opensearch.packages import ELASTICSEARCH_DEFAULT_VERSION
71
+ from localstack.state import StateVisitor
71
72
 
72
73
 
73
74
  def _version_to_opensearch(
@@ -208,6 +209,11 @@ def exception_mapper():
208
209
 
209
210
 
210
211
  class EsProvider(EsApi):
212
+ def accept_state_visitor(self, visitor: StateVisitor):
213
+ # ES state entirely depends on `opensearch`, and delegates its entire state to it
214
+ # we do not need to manage state in ES
215
+ pass
216
+
211
217
  def create_elasticsearch_domain(
212
218
  self,
213
219
  context: RequestContext,
@@ -168,6 +168,7 @@ from localstack.services.events.utils import (
168
168
  recursive_remove_none_values_from_dict,
169
169
  )
170
170
  from localstack.services.plugins import ServiceLifecycleHook
171
+ from localstack.state import StateVisitor
171
172
  from localstack.utils.common import truncate
172
173
  from localstack.utils.event_matcher import matches_event
173
174
  from localstack.utils.strings import long_uid
@@ -246,6 +247,9 @@ class EventsProvider(EventsApi, ServiceLifecycleHook):
246
247
  self._connection_service_store: ConnectionServiceDict = {}
247
248
  self._api_destination_service_store: ApiDestinationServiceDict = {}
248
249
 
250
+ def accept_state_visitor(self, visitor: StateVisitor):
251
+ visitor.visit(events_stores)
252
+
249
253
  def on_before_start(self):
250
254
  JobScheduler.start()
251
255