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.
Files changed (158) 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 +1178 -12
  4. localstack/aws/api/iam/__init__.py +228 -0
  5. localstack/aws/api/kms/__init__.py +1 -0
  6. localstack/aws/api/lambda_/__init__.py +1034 -66
  7. localstack/aws/api/logs/__init__.py +500 -0
  8. localstack/aws/api/opensearch/__init__.py +100 -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 +45 -0
  12. localstack/aws/api/route53resolver/__init__.py +1 -0
  13. localstack/aws/api/s3/__init__.py +64 -0
  14. localstack/aws/api/s3control/__init__.py +19 -0
  15. localstack/aws/api/secretsmanager/__init__.py +37 -23
  16. localstack/aws/api/stepfunctions/__init__.py +52 -10
  17. localstack/aws/api/sts/__init__.py +52 -0
  18. localstack/aws/connect.py +35 -15
  19. localstack/aws/handlers/logging.py +8 -4
  20. localstack/aws/handlers/service.py +11 -2
  21. localstack/aws/protocol/serializer.py +1 -1
  22. localstack/config.py +8 -0
  23. localstack/constants.py +3 -0
  24. localstack/deprecations.py +0 -6
  25. localstack/dev/kubernetes/__main__.py +39 -14
  26. localstack/runtime/analytics.py +11 -0
  27. localstack/services/acm/provider.py +17 -1
  28. localstack/services/apigateway/legacy/provider.py +28 -15
  29. localstack/services/cloudformation/engine/template_preparer.py +6 -2
  30. localstack/services/cloudformation/engine/v2/change_set_model.py +9 -0
  31. localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +15 -1
  32. localstack/services/cloudformation/engine/v2/change_set_resource_support_checker.py +114 -0
  33. localstack/services/cloudformation/provider.py +26 -1
  34. localstack/services/cloudformation/provider_utils.py +20 -0
  35. localstack/services/cloudformation/resource_provider.py +5 -4
  36. localstack/services/cloudformation/scaffolding/__main__.py +94 -22
  37. localstack/services/cloudformation/v2/provider.py +41 -0
  38. localstack/services/cloudwatch/provider.py +10 -3
  39. localstack/services/cloudwatch/provider_v2.py +6 -3
  40. localstack/services/configservice/provider.py +5 -1
  41. localstack/services/dynamodb/provider.py +1 -0
  42. localstack/services/dynamodb/v2/provider.py +1 -0
  43. localstack/services/dynamodbstreams/provider.py +6 -0
  44. localstack/services/dynamodbstreams/v2/provider.py +6 -0
  45. localstack/services/ec2/provider.py +6 -0
  46. localstack/services/es/provider.py +6 -0
  47. localstack/services/events/provider.py +4 -0
  48. localstack/services/events/v1/provider.py +9 -0
  49. localstack/services/firehose/provider.py +5 -0
  50. localstack/services/iam/provider.py +4 -0
  51. localstack/services/kinesis/packages.py +1 -1
  52. localstack/services/kms/models.py +16 -22
  53. localstack/services/kms/provider.py +4 -0
  54. localstack/services/lambda_/analytics.py +11 -2
  55. localstack/services/lambda_/api_utils.py +37 -20
  56. localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +1 -1
  57. localstack/services/lambda_/invocation/assignment.py +4 -1
  58. localstack/services/lambda_/invocation/event_manager.py +15 -11
  59. localstack/services/lambda_/invocation/execution_environment.py +21 -2
  60. localstack/services/lambda_/invocation/lambda_models.py +31 -2
  61. localstack/services/lambda_/invocation/lambda_service.py +62 -3
  62. localstack/services/lambda_/invocation/models.py +9 -1
  63. localstack/services/lambda_/invocation/version_manager.py +18 -3
  64. localstack/services/lambda_/provider.py +307 -106
  65. localstack/services/lambda_/resource_providers/aws_lambda_function.py +33 -1
  66. localstack/services/lambda_/runtimes.py +3 -1
  67. localstack/services/logs/provider.py +9 -0
  68. localstack/services/opensearch/packages.py +34 -20
  69. localstack/services/opensearch/provider.py +53 -3
  70. localstack/services/resource_groups/provider.py +5 -1
  71. localstack/services/resourcegroupstaggingapi/provider.py +6 -1
  72. localstack/services/route53/provider.py +7 -0
  73. localstack/services/route53resolver/provider.py +5 -0
  74. localstack/services/s3/constants.py +5 -0
  75. localstack/services/s3/exceptions.py +9 -0
  76. localstack/services/s3/models.py +9 -1
  77. localstack/services/s3/provider.py +51 -43
  78. localstack/services/s3/utils.py +81 -15
  79. localstack/services/s3control/provider.py +107 -2
  80. localstack/services/s3control/validation.py +50 -0
  81. localstack/services/scheduler/provider.py +4 -2
  82. localstack/services/secretsmanager/provider.py +4 -0
  83. localstack/services/ses/provider.py +4 -0
  84. localstack/services/sns/constants.py +16 -1
  85. localstack/services/sns/provider.py +5 -0
  86. localstack/services/sns/publisher.py +15 -6
  87. localstack/services/sns/v2/models.py +9 -0
  88. localstack/services/sns/v2/provider.py +750 -19
  89. localstack/services/sns/v2/utils.py +12 -0
  90. localstack/services/sqs/constants.py +6 -0
  91. localstack/services/sqs/provider.py +9 -1
  92. localstack/services/sqs/resource_providers/aws_sqs_queue.py +61 -46
  93. localstack/services/ssm/provider.py +6 -0
  94. localstack/services/stepfunctions/asl/component/common/path/result_path.py +1 -1
  95. localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py +0 -1
  96. localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +0 -1
  97. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py +8 -8
  98. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/{mock_eval_utils.py → local_mock_eval_utils.py} +13 -9
  99. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py +6 -6
  100. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py +1 -1
  101. localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +4 -0
  102. localstack/services/stepfunctions/asl/component/test_state/state/base_mock.py +118 -0
  103. localstack/services/stepfunctions/asl/component/test_state/state/common.py +82 -0
  104. localstack/services/stepfunctions/asl/component/test_state/state/execution.py +139 -0
  105. localstack/services/stepfunctions/asl/component/test_state/state/map.py +77 -0
  106. localstack/services/stepfunctions/asl/component/test_state/state/task.py +44 -0
  107. localstack/services/stepfunctions/asl/eval/environment.py +30 -22
  108. localstack/services/stepfunctions/asl/eval/states.py +1 -1
  109. localstack/services/stepfunctions/asl/eval/test_state/environment.py +49 -9
  110. localstack/services/stepfunctions/asl/eval/test_state/program_state.py +22 -0
  111. localstack/services/stepfunctions/asl/jsonata/jsonata.py +5 -1
  112. localstack/services/stepfunctions/asl/parse/preprocessor.py +67 -24
  113. localstack/services/stepfunctions/asl/parse/test_state/asl_parser.py +5 -4
  114. localstack/services/stepfunctions/asl/parse/test_state/preprocessor.py +222 -31
  115. localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py +256 -22
  116. localstack/services/stepfunctions/backend/execution.py +10 -11
  117. localstack/services/stepfunctions/backend/execution_worker.py +5 -5
  118. localstack/services/stepfunctions/backend/test_state/execution.py +36 -0
  119. localstack/services/stepfunctions/backend/test_state/execution_worker.py +33 -1
  120. localstack/services/stepfunctions/backend/test_state/test_state_mock.py +127 -0
  121. localstack/services/stepfunctions/local_mocking/__init__.py +9 -0
  122. localstack/services/stepfunctions/{mocking → local_mocking}/mock_config.py +24 -17
  123. localstack/services/stepfunctions/provider.py +83 -25
  124. localstack/services/stepfunctions/test_state/mock_config.py +47 -0
  125. localstack/services/sts/provider.py +7 -0
  126. localstack/services/support/provider.py +5 -1
  127. localstack/services/swf/provider.py +5 -1
  128. localstack/services/transcribe/provider.py +7 -0
  129. localstack/testing/aws/lambda_utils.py +1 -1
  130. localstack/testing/aws/util.py +2 -1
  131. localstack/testing/config.py +1 -0
  132. localstack/testing/pytest/fixtures.py +28 -0
  133. localstack/testing/snapshots/transformer_utility.py +5 -0
  134. localstack/utils/analytics/publisher.py +37 -155
  135. localstack/utils/analytics/service_request_aggregator.py +6 -4
  136. localstack/utils/aws/arns.py +7 -0
  137. localstack/utils/aws/client_types.py +2 -4
  138. localstack/utils/batching.py +258 -0
  139. localstack/utils/bootstrap.py +2 -2
  140. localstack/utils/catalog/catalog.py +3 -2
  141. localstack/utils/collections.py +23 -11
  142. localstack/utils/container_utils/container_client.py +22 -13
  143. localstack/utils/container_utils/docker_cmd_client.py +6 -6
  144. localstack/version.py +2 -2
  145. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/METADATA +7 -7
  146. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/RECORD +155 -146
  147. localstack_core-4.12.1.dev18.dist-info/plux.json +1 -0
  148. localstack/services/stepfunctions/mocking/__init__.py +0 -0
  149. localstack/utils/batch_policy.py +0 -124
  150. localstack_core-4.10.1.dev42.dist-info/plux.json +0 -1
  151. /localstack/services/stepfunctions/{mocking → local_mocking}/mock_config_file.py +0 -0
  152. {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack +0 -0
  153. {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack-supervisor +0 -0
  154. {localstack_core-4.10.1.dev42.data → localstack_core-4.12.1.dev18.data}/scripts/localstack.bat +0 -0
  155. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/WHEEL +0 -0
  156. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/entry_points.txt +0 -0
  157. {localstack_core-4.10.1.dev42.dist-info → localstack_core-4.12.1.dev18.dist-info}/licenses/LICENSE.txt +0 -0
  158. {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 service_name == "s3" and endpoint_url:
519
- if re.match(r"https?://localhost(:[0-9]+)?", endpoint_url):
520
- endpoint_url = endpoint_url.replace("://localhost", f"://{get_s3_hostname()}")
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 config and config.region_name != AWS_REGION_US_EAST_1:
578
- if region_name == AWS_REGION_US_EAST_1:
579
- config = config.merge(Config(region_name=region_name))
580
-
581
- endpoint_url = endpoint_url or get_service_endpoint()
582
- if service_name == "s3":
583
- if re.match(r"https?://localhost(:[0-9]+)?", endpoint_url):
584
- endpoint_url = endpoint_url.replace("://localhost", f"://{get_s3_hostname()}")
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
- super().__init__(use_ssl=True, verify=True, session=session, config=config)
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._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
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"])
@@ -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, file_name: str):
319
- path = os.path.join(output_path, file_name)
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 {path}")
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
- if write:
420
- write_file(config, output_dir, config_file)
421
- write_file(overrides, output_dir, overrides_file)
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 k3d cluster create --config {config_file_path}\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():
@@ -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 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:
@@ -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<String>" | "CommaDelimitedList":
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