localstack-core 4.10.1.dev7__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.
Potentially problematic release.
This version of localstack-core might be problematic. Click here for more details.
- localstack/aws/api/acm/__init__.py +122 -122
- localstack/aws/api/apigateway/__init__.py +604 -561
- localstack/aws/api/cloudcontrol/__init__.py +63 -63
- localstack/aws/api/cloudformation/__init__.py +1201 -969
- localstack/aws/api/cloudwatch/__init__.py +375 -375
- localstack/aws/api/config/__init__.py +784 -786
- localstack/aws/api/dynamodb/__init__.py +753 -759
- localstack/aws/api/dynamodbstreams/__init__.py +74 -74
- localstack/aws/api/ec2/__init__.py +10062 -8826
- localstack/aws/api/es/__init__.py +453 -453
- localstack/aws/api/events/__init__.py +552 -552
- localstack/aws/api/firehose/__init__.py +541 -543
- localstack/aws/api/iam/__init__.py +866 -572
- localstack/aws/api/kinesis/__init__.py +235 -147
- localstack/aws/api/kms/__init__.py +341 -336
- localstack/aws/api/lambda_/__init__.py +974 -621
- localstack/aws/api/logs/__init__.py +988 -675
- localstack/aws/api/opensearch/__init__.py +903 -785
- localstack/aws/api/pipes/__init__.py +336 -336
- localstack/aws/api/redshift/__init__.py +1257 -1166
- localstack/aws/api/resource_groups/__init__.py +175 -175
- localstack/aws/api/resourcegroupstaggingapi/__init__.py +103 -67
- localstack/aws/api/route53/__init__.py +296 -254
- localstack/aws/api/route53resolver/__init__.py +397 -396
- localstack/aws/api/s3/__init__.py +1412 -1349
- localstack/aws/api/s3control/__init__.py +594 -594
- localstack/aws/api/scheduler/__init__.py +118 -118
- localstack/aws/api/secretsmanager/__init__.py +221 -216
- localstack/aws/api/ses/__init__.py +227 -227
- localstack/aws/api/sns/__init__.py +115 -115
- localstack/aws/api/sqs/__init__.py +100 -100
- localstack/aws/api/ssm/__init__.py +1977 -1971
- localstack/aws/api/stepfunctions/__init__.py +375 -333
- localstack/aws/api/sts/__init__.py +142 -66
- localstack/aws/api/support/__init__.py +112 -112
- localstack/aws/api/swf/__init__.py +378 -386
- localstack/aws/api/transcribe/__init__.py +425 -425
- localstack/aws/handlers/logging.py +8 -4
- localstack/aws/handlers/service.py +22 -3
- localstack/aws/protocol/parser.py +1 -1
- localstack/aws/protocol/serializer.py +1 -1
- localstack/aws/scaffold.py +15 -17
- localstack/cli/localstack.py +6 -1
- localstack/deprecations.py +0 -6
- localstack/dev/kubernetes/__main__.py +38 -3
- localstack/services/acm/provider.py +4 -0
- localstack/services/apigateway/helpers.py +5 -9
- localstack/services/apigateway/legacy/provider.py +60 -24
- localstack/services/apigateway/patches.py +0 -9
- localstack/services/cloudformation/engine/template_preparer.py +6 -2
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +12 -0
- localstack/services/cloudformation/provider.py +2 -2
- localstack/services/cloudformation/v2/provider.py +6 -6
- localstack/services/cloudwatch/provider.py +10 -3
- localstack/services/cloudwatch/provider_v2.py +6 -3
- localstack/services/configservice/provider.py +5 -1
- localstack/services/dynamodb/provider.py +1 -0
- localstack/services/dynamodb/v2/provider.py +1 -0
- localstack/services/dynamodbstreams/provider.py +6 -0
- localstack/services/dynamodbstreams/v2/provider.py +6 -0
- localstack/services/ec2/provider.py +6 -0
- localstack/services/es/provider.py +6 -0
- localstack/services/events/provider.py +4 -0
- localstack/services/events/v1/provider.py +9 -0
- localstack/services/firehose/provider.py +5 -0
- localstack/services/iam/provider.py +4 -0
- localstack/services/kinesis/packages.py +1 -1
- localstack/services/kms/models.py +44 -24
- localstack/services/kms/provider.py +97 -16
- localstack/services/lambda_/api_utils.py +40 -21
- localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +1 -1
- localstack/services/lambda_/invocation/assignment.py +4 -1
- localstack/services/lambda_/invocation/execution_environment.py +21 -2
- localstack/services/lambda_/invocation/lambda_models.py +27 -2
- localstack/services/lambda_/invocation/lambda_service.py +51 -3
- localstack/services/lambda_/invocation/models.py +9 -1
- localstack/services/lambda_/invocation/version_manager.py +18 -3
- localstack/services/lambda_/packages.py +1 -1
- localstack/services/lambda_/provider.py +240 -96
- localstack/services/lambda_/resource_providers/aws_lambda_function.py +33 -1
- localstack/services/lambda_/runtimes.py +10 -3
- localstack/services/logs/provider.py +45 -19
- localstack/services/opensearch/provider.py +53 -3
- localstack/services/resource_groups/provider.py +5 -1
- localstack/services/resourcegroupstaggingapi/provider.py +6 -1
- localstack/services/s3/provider.py +29 -16
- localstack/services/s3/utils.py +35 -14
- localstack/services/s3control/provider.py +101 -2
- localstack/services/s3control/validation.py +50 -0
- localstack/services/sns/constants.py +3 -1
- localstack/services/sns/publisher.py +15 -6
- localstack/services/sns/v2/models.py +30 -1
- localstack/services/sns/v2/provider.py +794 -31
- localstack/services/sns/v2/utils.py +20 -0
- localstack/services/sqs/models.py +37 -10
- localstack/services/stepfunctions/asl/component/common/path/result_path.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py +0 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +0 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py +8 -8
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/{mock_eval_utils.py → local_mock_eval_utils.py} +13 -9
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py +6 -6
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +4 -0
- localstack/services/stepfunctions/asl/component/test_state/state/base_mock.py +118 -0
- localstack/services/stepfunctions/asl/component/test_state/state/common.py +82 -0
- localstack/services/stepfunctions/asl/component/test_state/state/execution.py +139 -0
- localstack/services/stepfunctions/asl/component/test_state/state/map.py +77 -0
- localstack/services/stepfunctions/asl/component/test_state/state/task.py +44 -0
- localstack/services/stepfunctions/asl/eval/environment.py +30 -22
- localstack/services/stepfunctions/asl/eval/states.py +1 -1
- localstack/services/stepfunctions/asl/eval/test_state/environment.py +49 -9
- localstack/services/stepfunctions/asl/eval/test_state/program_state.py +22 -0
- localstack/services/stepfunctions/asl/jsonata/jsonata.py +5 -1
- localstack/services/stepfunctions/asl/parse/preprocessor.py +67 -24
- localstack/services/stepfunctions/asl/parse/test_state/asl_parser.py +5 -4
- localstack/services/stepfunctions/asl/parse/test_state/preprocessor.py +222 -31
- localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py +170 -22
- localstack/services/stepfunctions/backend/execution.py +6 -6
- localstack/services/stepfunctions/backend/execution_worker.py +5 -5
- localstack/services/stepfunctions/backend/test_state/execution.py +36 -0
- localstack/services/stepfunctions/backend/test_state/execution_worker.py +33 -1
- localstack/services/stepfunctions/backend/test_state/test_state_mock.py +127 -0
- localstack/services/stepfunctions/local_mocking/__init__.py +9 -0
- localstack/services/stepfunctions/{mocking → local_mocking}/mock_config.py +24 -17
- localstack/services/stepfunctions/provider.py +78 -27
- localstack/services/stepfunctions/test_state/mock_config.py +47 -0
- localstack/testing/pytest/fixtures.py +28 -0
- localstack/testing/snapshots/transformer_utility.py +7 -0
- localstack/testing/testselection/matching.py +0 -1
- localstack/utils/analytics/publisher.py +37 -155
- localstack/utils/analytics/service_request_aggregator.py +6 -4
- localstack/utils/aws/arns.py +7 -0
- localstack/utils/aws/client_types.py +0 -8
- localstack/utils/batching.py +258 -0
- localstack/utils/catalog/catalog_loader.py +111 -3
- localstack/utils/collections.py +23 -11
- localstack/utils/crypto.py +109 -0
- localstack/version.py +2 -2
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/METADATA +7 -6
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/RECORD +149 -141
- localstack_core-4.11.2.dev14.dist-info/plux.json +1 -0
- localstack/services/stepfunctions/mocking/__init__.py +0 -0
- localstack/utils/batch_policy.py +0 -124
- localstack_core-4.10.1.dev7.dist-info/plux.json +0 -1
- /localstack/services/stepfunctions/{mocking → local_mocking}/mock_config_file.py +0 -0
- {localstack_core-4.10.1.dev7.data → localstack_core-4.11.2.dev14.data}/scripts/localstack +0 -0
- {localstack_core-4.10.1.dev7.data → localstack_core-4.11.2.dev14.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.10.1.dev7.data → localstack_core-4.11.2.dev14.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/WHEEL +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/entry_points.txt +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.11.2.dev14.dist-info}/top_level.txt +0 -0
|
@@ -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.
|
|
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.
|
|
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,
|
|
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:
|
|
@@ -7,6 +7,7 @@ from collections import defaultdict
|
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
9
|
from botocore.model import OperationModel, ServiceModel
|
|
10
|
+
from plux.core.plugin import PluginDisabled
|
|
10
11
|
|
|
11
12
|
from localstack import config
|
|
12
13
|
from localstack.http import Response
|
|
@@ -157,14 +158,17 @@ class ServiceExceptionSerializer(ExceptionHandler):
|
|
|
157
158
|
def __init__(self):
|
|
158
159
|
self.handle_internal_failures = True
|
|
159
160
|
|
|
161
|
+
self._moto_service_exception = types.EllipsisType
|
|
162
|
+
self._moto_rest_error = types.EllipsisType
|
|
163
|
+
|
|
160
164
|
try:
|
|
161
165
|
import moto.core.exceptions
|
|
162
166
|
|
|
163
167
|
self._moto_service_exception = moto.core.exceptions.ServiceException
|
|
168
|
+
self._moto_rest_error = moto.core.exceptions.RESTError
|
|
164
169
|
except (ModuleNotFoundError, AttributeError) as exc:
|
|
165
170
|
# Moto may not be available in stripped-down versions of LocalStack, like LocalStack S3 image.
|
|
166
|
-
LOG.debug("Unable to set up Moto
|
|
167
|
-
self._moto_service_exception = types.EllipsisType
|
|
171
|
+
LOG.debug("Unable to set up Moto exception translation: %s", exc)
|
|
168
172
|
|
|
169
173
|
def __call__(
|
|
170
174
|
self,
|
|
@@ -199,6 +203,12 @@ class ServiceExceptionSerializer(ExceptionHandler):
|
|
|
199
203
|
code=exception.code,
|
|
200
204
|
message=exception.message,
|
|
201
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
|
+
)
|
|
202
212
|
|
|
203
213
|
elif not isinstance(exception, ServiceException):
|
|
204
214
|
if not self.handle_internal_failures:
|
|
@@ -222,7 +232,16 @@ class ServiceExceptionSerializer(ExceptionHandler):
|
|
|
222
232
|
operation = context.service.operation_model(context.service.operation_names[0])
|
|
223
233
|
msg = f"exception while calling {service_name} with unknown operation: {message}"
|
|
224
234
|
|
|
225
|
-
|
|
235
|
+
# Check for license restricted plugin message and set status code to 501
|
|
236
|
+
if (
|
|
237
|
+
isinstance(exception, PluginDisabled)
|
|
238
|
+
and "not part of the active license agreement"
|
|
239
|
+
in str(getattr(exception, "reason", "")).lower()
|
|
240
|
+
):
|
|
241
|
+
status_code = 501
|
|
242
|
+
msg = f"exception while calling {service_name}.{operation.name}: {str(getattr(exception, 'reason', ''))}"
|
|
243
|
+
else:
|
|
244
|
+
status_code = 501 if config.FAIL_FAST else 500
|
|
226
245
|
|
|
227
246
|
error = CommonServiceException(
|
|
228
247
|
"InternalError", msg, status_code=status_code
|
|
@@ -1032,7 +1032,7 @@ class BaseCBORRequestParser(RequestParser, ABC):
|
|
|
1032
1032
|
return method(stream, additional_info)
|
|
1033
1033
|
else:
|
|
1034
1034
|
raise ProtocolParserError(
|
|
1035
|
-
f"Unsupported
|
|
1035
|
+
f"Unsupported initial byte found for data item- "
|
|
1036
1036
|
f"Major type:{major_type}, Additional info: "
|
|
1037
1037
|
f"{additional_info}"
|
|
1038
1038
|
)
|
|
@@ -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
|
localstack/aws/scaffold.py
CHANGED
|
@@ -188,9 +188,7 @@ class ShapeNode:
|
|
|
188
188
|
if member in self.shape.required_members:
|
|
189
189
|
output.write(f" {member}: IO[{q}{to_valid_python_name(shape.name)}{q}]\n")
|
|
190
190
|
else:
|
|
191
|
-
output.write(
|
|
192
|
-
f" {member}: Optional[IO[{q}{to_valid_python_name(shape.name)}{q}]]\n"
|
|
193
|
-
)
|
|
191
|
+
output.write(f" {member}: {q}IO[{to_valid_python_name(shape.name)}] | None{q}\n")
|
|
194
192
|
del remaining_members[member]
|
|
195
193
|
# render the streaming payload first
|
|
196
194
|
if self.is_response and self.response_operation.has_streaming_output:
|
|
@@ -199,25 +197,26 @@ class ShapeNode:
|
|
|
199
197
|
shape_name = to_valid_python_name(shape.name)
|
|
200
198
|
if member in self.shape.required_members:
|
|
201
199
|
output.write(
|
|
202
|
-
f" {member}:
|
|
200
|
+
f" {member}: {q}{shape_name} | IO[{shape_name}] | Iterable[{shape_name}]{q}\n"
|
|
203
201
|
)
|
|
204
202
|
else:
|
|
205
203
|
output.write(
|
|
206
|
-
f" {member}:
|
|
204
|
+
f" {member}: {q}{shape_name} | IO[{shape_name}] | Iterable[{shape_name}] | None{q}\n"
|
|
207
205
|
)
|
|
208
206
|
del remaining_members[member]
|
|
209
207
|
|
|
210
208
|
for k, v in remaining_members.items():
|
|
209
|
+
shape_name = to_valid_python_name(v.name)
|
|
211
210
|
if k in self.shape.required_members:
|
|
212
211
|
if v.serialization.get("eventstream"):
|
|
213
|
-
output.write(f" {k}: Iterator[{q}{
|
|
212
|
+
output.write(f" {k}: Iterator[{q}{shape_name}{q}]\n")
|
|
214
213
|
else:
|
|
215
|
-
output.write(f" {k}: {q}{
|
|
214
|
+
output.write(f" {k}: {q}{shape_name}{q}\n")
|
|
216
215
|
else:
|
|
217
216
|
if v.serialization.get("eventstream"):
|
|
218
|
-
output.write(f" {k}: Iterator[{q}{
|
|
217
|
+
output.write(f" {k}: Iterator[{q}{shape_name}{q}]\n")
|
|
219
218
|
else:
|
|
220
|
-
output.write(f" {k}:
|
|
219
|
+
output.write(f" {k}: {q}{shape_name} | None{q}\n")
|
|
221
220
|
|
|
222
221
|
def _print_as_typed_dict(self, output, doc=True, quote_types=False):
|
|
223
222
|
name = to_valid_python_name(self.shape.name)
|
|
@@ -236,7 +235,7 @@ class ShapeNode:
|
|
|
236
235
|
if v.serialization.get("eventstream"):
|
|
237
236
|
output.write(f' "{k}": Iterator[{q}{member_name}{q}],\n')
|
|
238
237
|
else:
|
|
239
|
-
output.write(f' "{k}":
|
|
238
|
+
output.write(f' "{k}": {q}{member_name} | None{q},\n')
|
|
240
239
|
output.write("}, total=False)")
|
|
241
240
|
|
|
242
241
|
def print_shape_doc(self, output, shape):
|
|
@@ -256,11 +255,11 @@ class ShapeNode:
|
|
|
256
255
|
self._print_structure_declaration(output, doc, quote_types)
|
|
257
256
|
elif isinstance(shape, ListShape):
|
|
258
257
|
output.write(
|
|
259
|
-
f"{to_valid_python_name(shape.name)} =
|
|
258
|
+
f"{to_valid_python_name(shape.name)} = list[{q}{to_valid_python_name(shape.member.name)}{q}]"
|
|
260
259
|
)
|
|
261
260
|
elif isinstance(shape, MapShape):
|
|
262
261
|
output.write(
|
|
263
|
-
f"{to_valid_python_name(shape.name)} =
|
|
262
|
+
f"{to_valid_python_name(shape.name)} = dict[{q}{to_valid_python_name(shape.key.name)}{q}, {q}{to_valid_python_name(shape.value.name)}{q}]"
|
|
264
263
|
)
|
|
265
264
|
elif isinstance(shape, StringShape):
|
|
266
265
|
if shape.enum:
|
|
@@ -316,9 +315,8 @@ class ShapeNode:
|
|
|
316
315
|
def generate_service_types(output, service: ServiceModel, doc=True):
|
|
317
316
|
output.write("from datetime import datetime\n")
|
|
318
317
|
output.write("from enum import StrEnum\n")
|
|
319
|
-
output.write(
|
|
320
|
-
|
|
321
|
-
)
|
|
318
|
+
output.write("from typing import IO, TypedDict\n")
|
|
319
|
+
output.write("from collections.abc import Iterable, Iterator\n")
|
|
322
320
|
output.write("\n")
|
|
323
321
|
output.write(
|
|
324
322
|
"from localstack.aws.api import handler, RequestContext, ServiceException, ServiceRequest"
|
|
@@ -372,8 +370,8 @@ def generate_service_api(output, service: ServiceModel, doc=True):
|
|
|
372
370
|
|
|
373
371
|
output.write(f"class {class_name}:\n")
|
|
374
372
|
output.write("\n")
|
|
375
|
-
output.write(f' service = "{service.service_name}"\n')
|
|
376
|
-
output.write(f' version = "{service.api_version}"\n')
|
|
373
|
+
output.write(f' service: str = "{service.service_name}"\n')
|
|
374
|
+
output.write(f' version: str = "{service.api_version}"\n')
|
|
377
375
|
for op_name in service.operation_names:
|
|
378
376
|
operation: OperationModel = service.operation_model(op_name)
|
|
379
377
|
|
localstack/cli/localstack.py
CHANGED
|
@@ -433,7 +433,7 @@ def _print_service_table(services: dict[str, str]) -> None:
|
|
|
433
433
|
|
|
434
434
|
@localstack.command(name="start", short_help="Start LocalStack")
|
|
435
435
|
@click.option("--docker", is_flag=True, help="Start LocalStack in a docker container [default]")
|
|
436
|
-
@click.option("--host", is_flag=True, help="Start LocalStack directly on the host")
|
|
436
|
+
@click.option("--host", is_flag=True, help="Start LocalStack directly on the host", deprecated=True)
|
|
437
437
|
@click.option("--no-banner", is_flag=True, help="Disable LocalStack banner", default=False)
|
|
438
438
|
@click.option(
|
|
439
439
|
"-d", "--detached", is_flag=True, help="Start LocalStack in the background", default=False
|
|
@@ -532,6 +532,11 @@ def cmd_start(
|
|
|
532
532
|
console.log("starting LocalStack in Docker mode :whale:")
|
|
533
533
|
|
|
534
534
|
if host:
|
|
535
|
+
console.log(
|
|
536
|
+
"Warning: Starting LocalStack in host mode from the CLI is deprecated and will be removed soon. Please use the default Docker mode instead.",
|
|
537
|
+
style="bold red",
|
|
538
|
+
)
|
|
539
|
+
|
|
535
540
|
# call hooks to prepare host
|
|
536
541
|
bootstrap.prepare_host(console)
|
|
537
542
|
|
localstack/deprecations.py
CHANGED
|
@@ -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",
|
|
@@ -9,6 +9,7 @@ EDGE_SERVICE_NODE_PORT = 30066
|
|
|
9
9
|
NODE_PORT_START = 30010
|
|
10
10
|
SERVICE_PORT_START = 4510
|
|
11
11
|
NUMBER_OF_SERVICE_PORTS = 50
|
|
12
|
+
EDGE_SERVICE_DNS_PORT = 31053
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
@dataclasses.dataclass
|
|
@@ -144,7 +145,12 @@ def generate_mount_points(
|
|
|
144
145
|
return mount_points
|
|
145
146
|
|
|
146
147
|
|
|
147
|
-
def generate_k8s_cluster_config(
|
|
148
|
+
def generate_k8s_cluster_config(
|
|
149
|
+
mount_points: list[MountPoint],
|
|
150
|
+
port: int = 4566,
|
|
151
|
+
expose_dns: bool = False,
|
|
152
|
+
dns_port: int = 53,
|
|
153
|
+
):
|
|
148
154
|
volumes = [
|
|
149
155
|
{
|
|
150
156
|
"volume": f"{mount_point.host_path}:{mount_point.node_path}",
|
|
@@ -177,6 +183,16 @@ def generate_k8s_cluster_config(mount_points: list[MountPoint], port: int = 4566
|
|
|
177
183
|
},
|
|
178
184
|
]
|
|
179
185
|
|
|
186
|
+
if expose_dns:
|
|
187
|
+
ports.append(
|
|
188
|
+
{
|
|
189
|
+
"nodeFilters": [
|
|
190
|
+
"server:0",
|
|
191
|
+
],
|
|
192
|
+
"port": f"{dns_port}:{EDGE_SERVICE_DNS_PORT}",
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
|
|
180
196
|
config = {
|
|
181
197
|
"apiVersion": "k3d.io/v1alpha5",
|
|
182
198
|
"kind": "Simple",
|
|
@@ -279,7 +295,10 @@ def generate_k8s_helm_overrides(
|
|
|
279
295
|
"end": SERVICE_PORT_START + NUMBER_OF_SERVICE_PORTS,
|
|
280
296
|
"nodePortStart": NODE_PORT_START,
|
|
281
297
|
},
|
|
282
|
-
"dnsService":
|
|
298
|
+
"dnsService": {
|
|
299
|
+
"enabled": True,
|
|
300
|
+
"nodePort": EDGE_SERVICE_DNS_PORT,
|
|
301
|
+
},
|
|
283
302
|
}
|
|
284
303
|
overrides = {
|
|
285
304
|
"debug": True,
|
|
@@ -355,6 +374,18 @@ def print_file(content: dict, file_name: str):
|
|
|
355
374
|
help="Port to expose from the kubernetes node",
|
|
356
375
|
type=click.IntRange(0, 65535),
|
|
357
376
|
)
|
|
377
|
+
@click.option(
|
|
378
|
+
"--expose-dns",
|
|
379
|
+
is_flag=True,
|
|
380
|
+
default=False,
|
|
381
|
+
help="Expose DNS port from the kubernetes node.",
|
|
382
|
+
)
|
|
383
|
+
@click.option(
|
|
384
|
+
"--dns-port",
|
|
385
|
+
default=53,
|
|
386
|
+
help="DNS port to expose from the kubernetes node. It is applied only if --expose-dns is set.",
|
|
387
|
+
type=click.IntRange(0, 65535),
|
|
388
|
+
)
|
|
358
389
|
@click.argument("command", nargs=-1, required=False)
|
|
359
390
|
def run(
|
|
360
391
|
pro: bool = None,
|
|
@@ -367,13 +398,17 @@ def run(
|
|
|
367
398
|
command: str = None,
|
|
368
399
|
env: list[str] = None,
|
|
369
400
|
port: int = None,
|
|
401
|
+
expose_dns: bool = False,
|
|
402
|
+
dns_port: int = 53,
|
|
370
403
|
):
|
|
371
404
|
"""
|
|
372
405
|
A tool for localstack developers to generate the kubernetes cluster configuration file and the overrides to mount the localstack code into the cluster.
|
|
373
406
|
"""
|
|
374
407
|
mount_points = generate_mount_points(pro, mount_moto, mount_entrypoints)
|
|
375
408
|
|
|
376
|
-
config = generate_k8s_cluster_config(
|
|
409
|
+
config = generate_k8s_cluster_config(
|
|
410
|
+
mount_points, port=port, expose_dns=expose_dns, dns_port=dns_port
|
|
411
|
+
)
|
|
377
412
|
|
|
378
413
|
overrides = generate_k8s_helm_overrides(mount_points, pro=pro, env=env)
|
|
379
414
|
|
|
@@ -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,
|
|
@@ -23,7 +23,7 @@ from localstack.aws.api.apigateway import (
|
|
|
23
23
|
IntegrationType,
|
|
24
24
|
Model,
|
|
25
25
|
NotFoundException,
|
|
26
|
-
|
|
26
|
+
PutMode,
|
|
27
27
|
RequestValidator,
|
|
28
28
|
)
|
|
29
29
|
from localstack.constants import (
|
|
@@ -39,8 +39,7 @@ from localstack.services.apigateway.models import (
|
|
|
39
39
|
apigateway_stores,
|
|
40
40
|
)
|
|
41
41
|
from localstack.utils import common
|
|
42
|
-
from localstack.utils.
|
|
43
|
-
from localstack.utils.strings import short_uid, to_bytes, to_str
|
|
42
|
+
from localstack.utils.strings import short_uid, to_bytes
|
|
44
43
|
from localstack.utils.urls import localstack_host
|
|
45
44
|
|
|
46
45
|
LOG = logging.getLogger(__name__)
|
|
@@ -472,11 +471,9 @@ def add_documentation_parts(rest_api_container, documentation):
|
|
|
472
471
|
|
|
473
472
|
|
|
474
473
|
def import_api_from_openapi_spec(
|
|
475
|
-
rest_api: MotoRestAPI, context: RequestContext,
|
|
474
|
+
rest_api: MotoRestAPI, context: RequestContext, open_api_spec: dict, mode: PutMode
|
|
476
475
|
) -> tuple[MotoRestAPI, list[str]]:
|
|
477
476
|
"""Import an API from an OpenAPI spec document"""
|
|
478
|
-
body = parse_json_or_yaml(to_str(request["body"].read()))
|
|
479
|
-
|
|
480
477
|
warnings = []
|
|
481
478
|
|
|
482
479
|
# TODO There is an issue with the botocore specs so the parameters doesn't get populated as it should
|
|
@@ -484,15 +481,14 @@ def import_api_from_openapi_spec(
|
|
|
484
481
|
# query_params = request.get("parameters") or {}
|
|
485
482
|
query_params: dict = context.request.values.to_dict()
|
|
486
483
|
|
|
487
|
-
resolved_schema = resolve_references(copy.deepcopy(
|
|
484
|
+
resolved_schema = resolve_references(copy.deepcopy(open_api_spec), rest_api_id=rest_api.id)
|
|
488
485
|
account_id = context.account_id
|
|
489
486
|
region_name = context.region
|
|
490
487
|
|
|
491
488
|
# TODO:
|
|
492
|
-
# 1.
|
|
489
|
+
# 1. properly apply the mode (overwrite or merge)
|
|
493
490
|
# for now, it only considers it for the binaryMediaTypes
|
|
494
491
|
# 2. validate the document type, "swagger" or "openapi"
|
|
495
|
-
mode = request.get("mode", "merge")
|
|
496
492
|
|
|
497
493
|
rest_api.version = (
|
|
498
494
|
str(version) if (version := resolved_schema.get("info", {}).get("version")) else None
|
|
@@ -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
|
|
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
|
|
@@ -476,11 +488,19 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
476
488
|
|
|
477
489
|
@handler("PutRestApi", expand=False)
|
|
478
490
|
def put_rest_api(self, context: RequestContext, request: PutRestApiRequest) -> RestApi:
|
|
491
|
+
body_data = request["body"].read()
|
|
492
|
+
try:
|
|
493
|
+
openapi_spec = parse_json_or_yaml(to_str(body_data))
|
|
494
|
+
except Exception:
|
|
495
|
+
raise BadRequestException("Invalid OpenAPI input.")
|
|
479
496
|
# TODO: take into account the mode: overwrite or merge
|
|
480
497
|
# the default is now `merge`, but we are removing everything
|
|
481
498
|
rest_api = get_moto_rest_api(context, request["restApiId"])
|
|
482
499
|
rest_api, warnings = import_api_from_openapi_spec(
|
|
483
|
-
rest_api,
|
|
500
|
+
rest_api,
|
|
501
|
+
context=context,
|
|
502
|
+
open_api_spec=openapi_spec,
|
|
503
|
+
mode=request.get("mode") or PutMode.merge,
|
|
484
504
|
)
|
|
485
505
|
|
|
486
506
|
rest_api.root_resource_id = get_moto_rest_api_root_resource(rest_api)
|
|
@@ -503,20 +523,21 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
503
523
|
self,
|
|
504
524
|
context: RequestContext,
|
|
505
525
|
domain_name: String,
|
|
506
|
-
certificate_name: String = None,
|
|
507
|
-
certificate_body: String = None,
|
|
508
|
-
certificate_private_key: String = None,
|
|
509
|
-
certificate_chain: String = None,
|
|
510
|
-
certificate_arn: String = None,
|
|
511
|
-
regional_certificate_name: String = None,
|
|
512
|
-
regional_certificate_arn: String = None,
|
|
513
|
-
endpoint_configuration: EndpointConfiguration = None,
|
|
514
|
-
tags: MapOfStringToString = None,
|
|
515
|
-
security_policy: SecurityPolicy = None,
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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,
|
|
520
541
|
**kwargs,
|
|
521
542
|
) -> DomainName:
|
|
522
543
|
if not domain_name:
|
|
@@ -1512,7 +1533,10 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
1512
1533
|
**kwargs,
|
|
1513
1534
|
) -> DocumentationPartIds:
|
|
1514
1535
|
body_data = body.read()
|
|
1515
|
-
|
|
1536
|
+
try:
|
|
1537
|
+
openapi_spec = parse_json_or_yaml(to_str(body_data))
|
|
1538
|
+
except Exception:
|
|
1539
|
+
raise BadRequestException("Unable to build importer with provided input.")
|
|
1516
1540
|
|
|
1517
1541
|
rest_api_container = get_rest_api_container(context, rest_api_id=rest_api_id)
|
|
1518
1542
|
|
|
@@ -2012,7 +2036,11 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
2012
2036
|
body_data = body.read()
|
|
2013
2037
|
|
|
2014
2038
|
# create rest api
|
|
2015
|
-
|
|
2039
|
+
try:
|
|
2040
|
+
openapi_spec = parse_json_or_yaml(to_str(body_data))
|
|
2041
|
+
except Exception:
|
|
2042
|
+
raise BadRequestException("Invalid OpenAPI input.")
|
|
2043
|
+
|
|
2016
2044
|
create_api_request = CreateRestApiRequest(name=openapi_spec.get("info").get("title"))
|
|
2017
2045
|
create_api_context = create_custom_context(
|
|
2018
2046
|
context,
|
|
@@ -2053,12 +2081,20 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
2053
2081
|
**kwargs,
|
|
2054
2082
|
) -> Integration:
|
|
2055
2083
|
try:
|
|
2056
|
-
|
|
2057
|
-
except
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
raise
|
|
2084
|
+
moto_rest_api = get_moto_rest_api(context, rest_api_id)
|
|
2085
|
+
except NotFoundException:
|
|
2086
|
+
raise NotFoundException("Invalid Resource identifier specified")
|
|
2087
|
+
|
|
2088
|
+
if not (moto_resource := moto_rest_api.resources.get(resource_id)):
|
|
2089
|
+
raise NotFoundException("Invalid Resource identifier specified")
|
|
2090
|
+
|
|
2091
|
+
if not (moto_method := moto_resource.resource_methods.get(http_method)):
|
|
2092
|
+
raise NotFoundException("Invalid Method identifier specified")
|
|
2093
|
+
|
|
2094
|
+
if not moto_method.method_integration:
|
|
2095
|
+
raise NotFoundException("Invalid Integration identifier specified")
|
|
2096
|
+
|
|
2097
|
+
response: Integration = call_moto(context)
|
|
2062
2098
|
|
|
2063
2099
|
if integration_responses := response.get("integrationResponses"):
|
|
2064
2100
|
for integration_response in integration_responses.values():
|
|
@@ -5,7 +5,6 @@ import logging
|
|
|
5
5
|
from moto.apigateway import models as apigateway_models
|
|
6
6
|
from moto.apigateway.exceptions import (
|
|
7
7
|
DeploymentNotFoundException,
|
|
8
|
-
NoIntegrationDefined,
|
|
9
8
|
RestAPINotFound,
|
|
10
9
|
StageStillActive,
|
|
11
10
|
)
|
|
@@ -113,14 +112,6 @@ def apply_patches():
|
|
|
113
112
|
)
|
|
114
113
|
return result
|
|
115
114
|
|
|
116
|
-
# patch integration error responses
|
|
117
|
-
@patch(apigateway_models.Resource.get_integration)
|
|
118
|
-
def apigateway_models_resource_get_integration(fn, self, method_type):
|
|
119
|
-
resource_method = self.resource_methods.get(method_type, {})
|
|
120
|
-
if not resource_method.method_integration:
|
|
121
|
-
raise NoIntegrationDefined()
|
|
122
|
-
return resource_method.method_integration
|
|
123
|
-
|
|
124
115
|
@patch(apigateway_models.RestAPI.to_dict)
|
|
125
116
|
def apigateway_models_rest_api_to_dict(fn, self):
|
|
126
117
|
resp = fn(self)
|
|
@@ -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
|
|
21
|
-
|
|
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])
|
|
@@ -952,8 +952,8 @@ class CloudformationProvider(CloudformationApi):
|
|
|
952
952
|
def describe_stack_events(
|
|
953
953
|
self,
|
|
954
954
|
context: RequestContext,
|
|
955
|
-
stack_name: StackName
|
|
956
|
-
next_token: NextToken = None,
|
|
955
|
+
stack_name: StackName,
|
|
956
|
+
next_token: NextToken | None = None,
|
|
957
957
|
**kwargs,
|
|
958
958
|
) -> DescribeStackEventsOutput:
|
|
959
959
|
if stack_name is None:
|