localstack-core 4.10.1.dev42__py3-none-any.whl → 4.12.1.dev18__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- localstack/aws/api/apigateway/__init__.py +42 -0
- localstack/aws/api/cloudformation/__init__.py +161 -0
- localstack/aws/api/ec2/__init__.py +1178 -12
- localstack/aws/api/iam/__init__.py +228 -0
- localstack/aws/api/kms/__init__.py +1 -0
- localstack/aws/api/lambda_/__init__.py +1034 -66
- localstack/aws/api/logs/__init__.py +500 -0
- localstack/aws/api/opensearch/__init__.py +100 -0
- localstack/aws/api/redshift/__init__.py +69 -0
- localstack/aws/api/resourcegroupstaggingapi/__init__.py +36 -0
- localstack/aws/api/route53/__init__.py +45 -0
- localstack/aws/api/route53resolver/__init__.py +1 -0
- localstack/aws/api/s3/__init__.py +64 -0
- localstack/aws/api/s3control/__init__.py +19 -0
- localstack/aws/api/secretsmanager/__init__.py +37 -23
- localstack/aws/api/stepfunctions/__init__.py +52 -10
- localstack/aws/api/sts/__init__.py +52 -0
- localstack/aws/connect.py +35 -15
- localstack/aws/handlers/logging.py +8 -4
- localstack/aws/handlers/service.py +11 -2
- localstack/aws/protocol/serializer.py +1 -1
- localstack/config.py +8 -0
- localstack/constants.py +3 -0
- localstack/deprecations.py +0 -6
- localstack/dev/kubernetes/__main__.py +39 -14
- localstack/runtime/analytics.py +11 -0
- localstack/services/acm/provider.py +17 -1
- localstack/services/apigateway/legacy/provider.py +28 -15
- localstack/services/cloudformation/engine/template_preparer.py +6 -2
- localstack/services/cloudformation/engine/v2/change_set_model.py +9 -0
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +15 -1
- localstack/services/cloudformation/engine/v2/change_set_resource_support_checker.py +114 -0
- localstack/services/cloudformation/provider.py +26 -1
- localstack/services/cloudformation/provider_utils.py +20 -0
- localstack/services/cloudformation/resource_provider.py +5 -4
- localstack/services/cloudformation/scaffolding/__main__.py +94 -22
- localstack/services/cloudformation/v2/provider.py +41 -0
- localstack/services/cloudwatch/provider.py +10 -3
- localstack/services/cloudwatch/provider_v2.py +6 -3
- localstack/services/configservice/provider.py +5 -1
- localstack/services/dynamodb/provider.py +1 -0
- localstack/services/dynamodb/v2/provider.py +1 -0
- localstack/services/dynamodbstreams/provider.py +6 -0
- localstack/services/dynamodbstreams/v2/provider.py +6 -0
- localstack/services/ec2/provider.py +6 -0
- localstack/services/es/provider.py +6 -0
- localstack/services/events/provider.py +4 -0
- localstack/services/events/v1/provider.py +9 -0
- localstack/services/firehose/provider.py +5 -0
- localstack/services/iam/provider.py +4 -0
- localstack/services/kinesis/packages.py +1 -1
- localstack/services/kms/models.py +16 -22
- localstack/services/kms/provider.py +4 -0
- localstack/services/lambda_/analytics.py +11 -2
- localstack/services/lambda_/api_utils.py +37 -20
- localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +1 -1
- localstack/services/lambda_/invocation/assignment.py +4 -1
- localstack/services/lambda_/invocation/event_manager.py +15 -11
- localstack/services/lambda_/invocation/execution_environment.py +21 -2
- localstack/services/lambda_/invocation/lambda_models.py +31 -2
- localstack/services/lambda_/invocation/lambda_service.py +62 -3
- localstack/services/lambda_/invocation/models.py +9 -1
- localstack/services/lambda_/invocation/version_manager.py +18 -3
- localstack/services/lambda_/provider.py +307 -106
- localstack/services/lambda_/resource_providers/aws_lambda_function.py +33 -1
- localstack/services/lambda_/runtimes.py +3 -1
- localstack/services/logs/provider.py +9 -0
- localstack/services/opensearch/packages.py +34 -20
- localstack/services/opensearch/provider.py +53 -3
- localstack/services/resource_groups/provider.py +5 -1
- localstack/services/resourcegroupstaggingapi/provider.py +6 -1
- localstack/services/route53/provider.py +7 -0
- localstack/services/route53resolver/provider.py +5 -0
- localstack/services/s3/constants.py +5 -0
- localstack/services/s3/exceptions.py +9 -0
- localstack/services/s3/models.py +9 -1
- localstack/services/s3/provider.py +51 -43
- localstack/services/s3/utils.py +81 -15
- localstack/services/s3control/provider.py +107 -2
- localstack/services/s3control/validation.py +50 -0
- localstack/services/scheduler/provider.py +4 -2
- localstack/services/secretsmanager/provider.py +4 -0
- localstack/services/ses/provider.py +4 -0
- localstack/services/sns/constants.py +16 -1
- localstack/services/sns/provider.py +5 -0
- localstack/services/sns/publisher.py +15 -6
- localstack/services/sns/v2/models.py +9 -0
- localstack/services/sns/v2/provider.py +750 -19
- localstack/services/sns/v2/utils.py +12 -0
- localstack/services/sqs/constants.py +6 -0
- localstack/services/sqs/provider.py +9 -1
- localstack/services/sqs/resource_providers/aws_sqs_queue.py +61 -46
- localstack/services/ssm/provider.py +6 -0
- localstack/services/stepfunctions/asl/component/common/path/result_path.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py +0 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +0 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py +8 -8
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/{mock_eval_utils.py → local_mock_eval_utils.py} +13 -9
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py +6 -6
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +4 -0
- localstack/services/stepfunctions/asl/component/test_state/state/base_mock.py +118 -0
- localstack/services/stepfunctions/asl/component/test_state/state/common.py +82 -0
- localstack/services/stepfunctions/asl/component/test_state/state/execution.py +139 -0
- localstack/services/stepfunctions/asl/component/test_state/state/map.py +77 -0
- localstack/services/stepfunctions/asl/component/test_state/state/task.py +44 -0
- localstack/services/stepfunctions/asl/eval/environment.py +30 -22
- localstack/services/stepfunctions/asl/eval/states.py +1 -1
- localstack/services/stepfunctions/asl/eval/test_state/environment.py +49 -9
- localstack/services/stepfunctions/asl/eval/test_state/program_state.py +22 -0
- localstack/services/stepfunctions/asl/jsonata/jsonata.py +5 -1
- localstack/services/stepfunctions/asl/parse/preprocessor.py +67 -24
- localstack/services/stepfunctions/asl/parse/test_state/asl_parser.py +5 -4
- localstack/services/stepfunctions/asl/parse/test_state/preprocessor.py +222 -31
- localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py +256 -22
- localstack/services/stepfunctions/backend/execution.py +10 -11
- localstack/services/stepfunctions/backend/execution_worker.py +5 -5
- localstack/services/stepfunctions/backend/test_state/execution.py +36 -0
- localstack/services/stepfunctions/backend/test_state/execution_worker.py +33 -1
- localstack/services/stepfunctions/backend/test_state/test_state_mock.py +127 -0
- localstack/services/stepfunctions/local_mocking/__init__.py +9 -0
- localstack/services/stepfunctions/{mocking → local_mocking}/mock_config.py +24 -17
- localstack/services/stepfunctions/provider.py +83 -25
- localstack/services/stepfunctions/test_state/mock_config.py +47 -0
- localstack/services/sts/provider.py +7 -0
- localstack/services/support/provider.py +5 -1
- localstack/services/swf/provider.py +5 -1
- localstack/services/transcribe/provider.py +7 -0
- localstack/testing/aws/lambda_utils.py +1 -1
- localstack/testing/aws/util.py +2 -1
- localstack/testing/config.py +1 -0
- localstack/testing/pytest/fixtures.py +28 -0
- localstack/testing/snapshots/transformer_utility.py +5 -0
- localstack/utils/analytics/publisher.py +37 -155
- localstack/utils/analytics/service_request_aggregator.py +6 -4
- localstack/utils/aws/arns.py +7 -0
- localstack/utils/aws/client_types.py +2 -4
- localstack/utils/batching.py +258 -0
- localstack/utils/bootstrap.py +2 -2
- localstack/utils/catalog/catalog.py +3 -2
- localstack/utils/collections.py +23 -11
- localstack/utils/container_utils/container_client.py +22 -13
- localstack/utils/container_utils/docker_cmd_client.py +6 -6
- localstack/version.py +2 -2
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/METADATA +7 -7
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/RECORD +155 -146
- localstack_core-4.12.1.dev18.dist-info/plux.json +1 -0
- localstack/services/stepfunctions/mocking/__init__.py +0 -0
- localstack/utils/batch_policy.py +0 -124
- localstack_core-4.10.1.dev42.dist-info/plux.json +0 -1
- /localstack/services/stepfunctions/{mocking → local_mocking}/mock_config_file.py +0 -0
- {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack +0 -0
- {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/WHEEL +0 -0
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/entry_points.txt +0 -0
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/top_level.txt +0 -0
localstack/aws/connect.py
CHANGED
|
@@ -7,6 +7,7 @@ LocalStack providers.
|
|
|
7
7
|
|
|
8
8
|
import json
|
|
9
9
|
import logging
|
|
10
|
+
import os
|
|
10
11
|
import re
|
|
11
12
|
import threading
|
|
12
13
|
from abc import ABC, abstractmethod
|
|
@@ -250,9 +251,10 @@ class ClientFactory(ABC):
|
|
|
250
251
|
def __init__(
|
|
251
252
|
self,
|
|
252
253
|
use_ssl: bool = False,
|
|
253
|
-
verify: bool = False,
|
|
254
|
+
verify: bool | str = False,
|
|
254
255
|
session: Session = None,
|
|
255
256
|
config: Config = None,
|
|
257
|
+
endpoint: str = None,
|
|
256
258
|
):
|
|
257
259
|
"""
|
|
258
260
|
:param use_ssl: Whether to use SSL
|
|
@@ -268,6 +270,7 @@ class ClientFactory(ABC):
|
|
|
268
270
|
self._verify = verify
|
|
269
271
|
self._config: Config = config or Config(max_pool_connections=MAX_POOL_CONNECTIONS)
|
|
270
272
|
self._session: Session = session or Session()
|
|
273
|
+
self._endpoint = endpoint
|
|
271
274
|
|
|
272
275
|
# make sure we consider our custom data paths for legacy specs (like SQS query protocol)
|
|
273
276
|
if LOCALSTACK_BUILTIN_DATA_PATH not in self._session._loader.search_paths:
|
|
@@ -514,10 +517,13 @@ class InternalClientFactory(ClientFactory):
|
|
|
514
517
|
else:
|
|
515
518
|
config = self._config.merge(config)
|
|
516
519
|
|
|
517
|
-
endpoint_url = endpoint_url or get_service_endpoint()
|
|
518
|
-
if
|
|
519
|
-
|
|
520
|
-
|
|
520
|
+
endpoint_url = endpoint_url or self._endpoint or get_service_endpoint()
|
|
521
|
+
if (
|
|
522
|
+
endpoint_url
|
|
523
|
+
and service_name == "s3"
|
|
524
|
+
and re.match(r"https?://localhost(:[0-9]+)?", endpoint_url)
|
|
525
|
+
):
|
|
526
|
+
endpoint_url = endpoint_url.replace("://localhost", f"://{get_s3_hostname()}")
|
|
521
527
|
|
|
522
528
|
return self._get_client(
|
|
523
529
|
service_name=service_name,
|
|
@@ -574,14 +580,20 @@ class ExternalClientFactory(ClientFactory):
|
|
|
574
580
|
# If the region in arg is non-default, it gives the arg the precedence
|
|
575
581
|
# But if the region in arg is default (us-east-1), it gives precedence to one in config
|
|
576
582
|
# Below: always give precedence to arg region
|
|
577
|
-
if
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
583
|
+
if (
|
|
584
|
+
config
|
|
585
|
+
and config.region_name != AWS_REGION_US_EAST_1
|
|
586
|
+
and region_name == AWS_REGION_US_EAST_1
|
|
587
|
+
):
|
|
588
|
+
config = config.merge(Config(region_name=region_name))
|
|
589
|
+
|
|
590
|
+
endpoint_url = endpoint_url or self._endpoint or get_service_endpoint()
|
|
591
|
+
if (
|
|
592
|
+
endpoint_url
|
|
593
|
+
and service_name == "s3"
|
|
594
|
+
and re.match(r"https?://localhost(:[0-9]+)?", endpoint_url)
|
|
595
|
+
):
|
|
596
|
+
endpoint_url = endpoint_url.replace("://localhost", f"://{get_s3_hostname()}")
|
|
585
597
|
|
|
586
598
|
# Prevent `PartialCredentialsError` when only access key ID is provided
|
|
587
599
|
# The value of secret access key is insignificant and can be set to anything
|
|
@@ -683,11 +695,19 @@ class ExternalBypassDnsClientFactory(ExternalAwsClientFactory):
|
|
|
683
695
|
session: Session = None,
|
|
684
696
|
config: Config = None,
|
|
685
697
|
):
|
|
686
|
-
|
|
698
|
+
if ca_cert := os.getenv("REQUESTS_CA_BUNDLE"):
|
|
699
|
+
LOG.debug("Creating External AWS Client with REQUESTS_CA_BUNDLE=%s", ca_cert)
|
|
700
|
+
|
|
701
|
+
super().__init__(
|
|
702
|
+
use_ssl=localstack_config.is_env_not_false("USE_SSL"),
|
|
703
|
+
verify=ca_cert or True,
|
|
704
|
+
session=session,
|
|
705
|
+
config=config,
|
|
706
|
+
)
|
|
687
707
|
|
|
688
708
|
def _get_client_post_hook(self, client: BaseClient) -> BaseClient:
|
|
689
709
|
client = super()._get_client_post_hook(client)
|
|
690
|
-
client._endpoint.http_session = ExternalBypassDnsSession()
|
|
710
|
+
client._endpoint.http_session = ExternalBypassDnsSession(verify=self._verify)
|
|
691
711
|
return client
|
|
692
712
|
|
|
693
713
|
|
|
@@ -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:
|
|
@@ -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
|
|
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
|
localstack/config.py
CHANGED
|
@@ -225,6 +225,11 @@ def parse_boolean_env(env_var_name: str) -> bool | None:
|
|
|
225
225
|
return None
|
|
226
226
|
|
|
227
227
|
|
|
228
|
+
def parse_comma_separated_list(env_var_name: str) -> list[str]:
|
|
229
|
+
"""Parse a comma separated list from the given environment variable."""
|
|
230
|
+
return os.environ.get(env_var_name, "").strip().split(",")
|
|
231
|
+
|
|
232
|
+
|
|
228
233
|
def is_env_true(env_var_name: str) -> bool:
|
|
229
234
|
"""Whether the given environment variable has a truthy value."""
|
|
230
235
|
return os.environ.get(env_var_name, "").lower().strip() in TRUE_STRINGS
|
|
@@ -1211,6 +1216,9 @@ CFN_PER_RESOURCE_TIMEOUT = int(os.environ.get("CFN_PER_RESOURCE_TIMEOUT") or 300
|
|
|
1211
1216
|
# EXPERIMENTAL
|
|
1212
1217
|
CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES = is_env_not_false("CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES")
|
|
1213
1218
|
|
|
1219
|
+
# Decrease the waiting time for resource deployment
|
|
1220
|
+
CFN_NO_WAIT_ITERATIONS: str | int | None = os.environ.get("CFN_NO_WAIT_ITERATIONS")
|
|
1221
|
+
|
|
1214
1222
|
# bind address of local DNS server
|
|
1215
1223
|
DNS_ADDRESS = os.environ.get("DNS_ADDRESS") or "0.0.0.0"
|
|
1216
1224
|
# port of the local DNS server
|
localstack/constants.py
CHANGED
|
@@ -117,6 +117,9 @@ LOCALSTACK_INFRA_PROCESS = "LOCALSTACK_INFRA_PROCESS"
|
|
|
117
117
|
# AWS region us-east-1
|
|
118
118
|
AWS_REGION_US_EAST_1 = "us-east-1"
|
|
119
119
|
|
|
120
|
+
# AWS region eu-west-1
|
|
121
|
+
AWS_REGION_EU_WEST_1 = "eu-west-1"
|
|
122
|
+
|
|
120
123
|
# environment variable to override max pool connections
|
|
121
124
|
try:
|
|
122
125
|
MAX_POOL_CONNECTIONS = int(os.environ["MAX_POOL_CONNECTIONS"])
|
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",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import dataclasses
|
|
2
2
|
import os
|
|
3
|
+
import shlex
|
|
4
|
+
import subprocess as sp
|
|
3
5
|
from typing import Literal
|
|
4
6
|
|
|
5
7
|
import click
|
|
@@ -315,12 +317,11 @@ def generate_k8s_helm_overrides(
|
|
|
315
317
|
return overrides
|
|
316
318
|
|
|
317
319
|
|
|
318
|
-
def write_file(content: dict, output_path: str
|
|
319
|
-
|
|
320
|
-
with open(path, "w") as f:
|
|
320
|
+
def write_file(content: dict, output_path: str):
|
|
321
|
+
with open(output_path, "w") as f:
|
|
321
322
|
f.write(yaml.dump(content))
|
|
322
323
|
f.close()
|
|
323
|
-
print(f"Generated file at {
|
|
324
|
+
print(f"Generated file at {output_path}")
|
|
324
325
|
|
|
325
326
|
|
|
326
327
|
def print_file(content: dict, file_name: str):
|
|
@@ -330,6 +331,22 @@ def print_file(content: dict, file_name: str):
|
|
|
330
331
|
print("=====================================")
|
|
331
332
|
|
|
332
333
|
|
|
334
|
+
def generate_k3d_command(config_file_path: str) -> str:
|
|
335
|
+
return f"k3d cluster create --config {config_file_path}"
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def generate_helm_command(overrides_file_path: str) -> str:
|
|
339
|
+
return f"helm upgrade --install localstack localstack/localstack -f {overrides_file_path}"
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def execute_deployment(config_file_path: str, overrides_file_path: str):
|
|
343
|
+
"""
|
|
344
|
+
Use the k3d and helm commands to create a cluster and deploy LocalStack in one command
|
|
345
|
+
"""
|
|
346
|
+
sp.check_call(shlex.split(generate_k3d_command(config_file_path)))
|
|
347
|
+
sp.check_call(shlex.split(generate_helm_command(overrides_file_path)))
|
|
348
|
+
|
|
349
|
+
|
|
333
350
|
@click.command("run")
|
|
334
351
|
@click.option(
|
|
335
352
|
"--pro", is_flag=True, default=None, help="Mount the localstack-pro code into the cluster."
|
|
@@ -386,6 +403,13 @@ def print_file(content: dict, file_name: str):
|
|
|
386
403
|
help="DNS port to expose from the kubernetes node. It is applied only if --expose-dns is set.",
|
|
387
404
|
type=click.IntRange(0, 65535),
|
|
388
405
|
)
|
|
406
|
+
@click.option(
|
|
407
|
+
"--execute",
|
|
408
|
+
"-x",
|
|
409
|
+
is_flag=True,
|
|
410
|
+
default=False,
|
|
411
|
+
help="Execute deployment from generated config files. Implies -w/--write.",
|
|
412
|
+
)
|
|
389
413
|
@click.argument("command", nargs=-1, required=False)
|
|
390
414
|
def run(
|
|
391
415
|
pro: bool = None,
|
|
@@ -400,6 +424,7 @@ def run(
|
|
|
400
424
|
port: int = None,
|
|
401
425
|
expose_dns: bool = False,
|
|
402
426
|
dns_port: int = 53,
|
|
427
|
+
execute: bool = False,
|
|
403
428
|
):
|
|
404
429
|
"""
|
|
405
430
|
A tool for localstack developers to generate the kubernetes cluster configuration file and the overrides to mount the localstack code into the cluster.
|
|
@@ -416,25 +441,25 @@ def run(
|
|
|
416
441
|
overrides_file = overrides_file or "overrides.yml"
|
|
417
442
|
config_file = config_file or "configuration.yml"
|
|
418
443
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
444
|
+
overrides_file_path = os.path.join(output_dir, overrides_file)
|
|
445
|
+
config_file_path = os.path.join(output_dir, config_file)
|
|
446
|
+
|
|
447
|
+
if write or execute:
|
|
448
|
+
write_file(config, config_file_path)
|
|
449
|
+
write_file(overrides, overrides_file_path)
|
|
450
|
+
if execute:
|
|
451
|
+
execute_deployment(config_file, overrides_file)
|
|
422
452
|
else:
|
|
423
453
|
print_file(config, config_file)
|
|
424
454
|
print_file(overrides, overrides_file)
|
|
425
455
|
|
|
426
|
-
overrides_file_path = os.path.join(output_dir, overrides_file)
|
|
427
|
-
config_file_path = os.path.join(output_dir, config_file)
|
|
428
|
-
|
|
429
456
|
print("\nTo create a k3d cluster with the generated configuration, follow these steps:")
|
|
430
457
|
print("1. Run the following command to create the cluster:")
|
|
431
|
-
print(f"\n
|
|
458
|
+
print(f"\n {generate_k3d_command(config_file_path)}\n")
|
|
432
459
|
|
|
433
460
|
print("2. Once the cluster is created, start LocalStack with the generated overrides:")
|
|
434
461
|
print("\n helm repo add localstack https://localstack.github.io/helm-charts # (if required)")
|
|
435
|
-
print(
|
|
436
|
-
f"\n helm upgrade --install localstack localstack/localstack -f {overrides_file_path}\n"
|
|
437
|
-
)
|
|
462
|
+
print(f"\n {generate_helm_command(overrides_file_path)}\n")
|
|
438
463
|
|
|
439
464
|
|
|
440
465
|
def main():
|
localstack/runtime/analytics.py
CHANGED
|
@@ -7,10 +7,14 @@ from localstack.utils.analytics import log
|
|
|
7
7
|
|
|
8
8
|
LOG = logging.getLogger(__name__)
|
|
9
9
|
|
|
10
|
+
# Config options for which both usage and values are reported in analytics.
|
|
11
|
+
# Important: This list must only contain options whose values do not contain PII or sensitive data.
|
|
10
12
|
TRACKED_ENV_VAR = [
|
|
11
13
|
"ACTIVATE_PRO",
|
|
12
14
|
"ALLOW_NONSTANDARD_REGIONS",
|
|
13
15
|
"BEDROCK_PREWARM",
|
|
16
|
+
"CFN_IGNORE_UNSUPPORTED_TYPE_CREATE",
|
|
17
|
+
"CFN_IGNORE_UNSUPPORTED_TYPE_UPDATE",
|
|
14
18
|
"CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES",
|
|
15
19
|
"CLOUDFRONT_LAMBDA_EDGE",
|
|
16
20
|
"CONTAINER_RUNTIME",
|
|
@@ -26,6 +30,7 @@ TRACKED_ENV_VAR = [
|
|
|
26
30
|
"DYNAMODB_IN_MEMORY",
|
|
27
31
|
"DYNAMODB_REMOVE_EXPIRED_ITEMS",
|
|
28
32
|
"EAGER_SERVICE_LOADING",
|
|
33
|
+
"EC2_DOCKER_INIT",
|
|
29
34
|
"EC2_VM_MANAGER",
|
|
30
35
|
"ECS_TASK_EXECUTOR",
|
|
31
36
|
"EDGE_PORT",
|
|
@@ -71,9 +76,15 @@ TRACKED_ENV_VAR = [
|
|
|
71
76
|
"USE_SSL",
|
|
72
77
|
]
|
|
73
78
|
|
|
79
|
+
# Config options for which only the usage is reported in analytics.
|
|
80
|
+
# Use this for options which may hold sensitive data or PII.
|
|
74
81
|
PRESENCE_ENV_VAR = [
|
|
75
82
|
"DATA_DIR",
|
|
76
83
|
"EDGE_FORWARD_URL", # Not functional; deprecated in 1.4.0, removed in 3.0.0
|
|
84
|
+
"EC2_HYPERVISOR_URI",
|
|
85
|
+
"EC2_REFERENCE_DOMAIN",
|
|
86
|
+
"EC2_LIBVIRT_NETWORK",
|
|
87
|
+
"EC2_LIBVIRT_POOL",
|
|
77
88
|
"GATEWAY_LISTEN",
|
|
78
89
|
"HOSTNAME",
|
|
79
90
|
"HOSTNAME_EXTERNAL",
|
|
@@ -10,12 +10,23 @@ 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
|
|
16
17
|
moto_settings.ACM_VALIDATION_WAIT = min(10, moto_settings.ACM_VALIDATION_WAIT)
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
@patch(acm_models.AWSCertificateManagerBackend.list_certificates)
|
|
21
|
+
def list_certificates(list_certificates_orig, self, statuses, includes):
|
|
22
|
+
# Normalize keyTypes filter to match our describe() output format (hyphens)
|
|
23
|
+
if includes and "keyTypes" in includes:
|
|
24
|
+
includes["keyTypes"] = [
|
|
25
|
+
kt.replace("RSA_", "RSA-").replace("EC_", "EC-") for kt in includes["keyTypes"]
|
|
26
|
+
]
|
|
27
|
+
return list_certificates_orig(self, statuses, includes)
|
|
28
|
+
|
|
29
|
+
|
|
19
30
|
@patch(acm_models.CertBundle.describe)
|
|
20
31
|
def describe(describe_orig, self):
|
|
21
32
|
# TODO fix! Terrible hack (for parity). Moto adds certain required fields only if status is PENDING_VALIDATION.
|
|
@@ -70,8 +81,10 @@ def describe(describe_orig, self):
|
|
|
70
81
|
cert[key] = value
|
|
71
82
|
cert["Serial"] = str(cert.get("Serial") or "")
|
|
72
83
|
|
|
73
|
-
if cert.get("KeyAlgorithm") in ["RSA_1024", "RSA_2048"]:
|
|
84
|
+
if cert.get("KeyAlgorithm") in ["RSA_1024", "RSA_2048", "RSA_3072", "RSA_4096"]:
|
|
74
85
|
cert["KeyAlgorithm"] = cert["KeyAlgorithm"].replace("RSA_", "RSA-")
|
|
86
|
+
if cert.get("KeyAlgorithm") in ["EC_prime256v1", "EC_secp384r1", "EC_secp521r1"]:
|
|
87
|
+
cert["KeyAlgorithm"] = cert["KeyAlgorithm"].replace("EC_", "EC-")
|
|
75
88
|
|
|
76
89
|
# add subject alternative names
|
|
77
90
|
if cert["DomainName"] not in sans:
|
|
@@ -100,6 +113,9 @@ def describe(describe_orig, self):
|
|
|
100
113
|
|
|
101
114
|
|
|
102
115
|
class AcmProvider(AcmApi):
|
|
116
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
117
|
+
visitor.visit(acm_models.acm_backends)
|
|
118
|
+
|
|
103
119
|
@handler("RequestCertificate", expand=False)
|
|
104
120
|
def request_certificate(
|
|
105
121
|
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
|
|
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
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
|
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:
|
|
@@ -1169,6 +1169,15 @@ class ChangeSetModel:
|
|
|
1169
1169
|
fn_transform,
|
|
1170
1170
|
],
|
|
1171
1171
|
)
|
|
1172
|
+
|
|
1173
|
+
# special case of where either the before or after state does not specify properties but
|
|
1174
|
+
# the resource was in the previous template
|
|
1175
|
+
if (
|
|
1176
|
+
terminal_value_type.change_type == ChangeType.UNCHANGED
|
|
1177
|
+
and properties.change_type != ChangeType.UNCHANGED
|
|
1178
|
+
):
|
|
1179
|
+
change_type = ChangeType.MODIFIED
|
|
1180
|
+
|
|
1172
1181
|
requires_replacement = self._resolve_requires_replacement(
|
|
1173
1182
|
node_properties=properties, resource_type=terminal_value_type
|
|
1174
1183
|
)
|
|
@@ -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])
|
|
@@ -1057,7 +1069,9 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
1057
1069
|
|
|
1058
1070
|
def _resolve_parameter_type(value: str, type_: str) -> Any:
|
|
1059
1071
|
match type_:
|
|
1060
|
-
case "List<
|
|
1072
|
+
case s if re.match(r"List<[^>]+>", s):
|
|
1073
|
+
return [item.strip() for item in value.split(",")]
|
|
1074
|
+
case "CommaDelimitedList":
|
|
1061
1075
|
return [item.strip() for item in value.split(",")]
|
|
1062
1076
|
case "Number":
|
|
1063
1077
|
# TODO: validate the parameter type at template parse time (or whatever is in parity with AWS) so we know this cannot fail
|