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.

Potentially problematic release.


This version of localstack-core might be problematic. Click here for more details.

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
@@ -168,6 +168,7 @@ from localstack.services.events.utils import (
168
168
  recursive_remove_none_values_from_dict,
169
169
  )
170
170
  from localstack.services.plugins import ServiceLifecycleHook
171
+ from localstack.state import StateVisitor
171
172
  from localstack.utils.common import truncate
172
173
  from localstack.utils.event_matcher import matches_event
173
174
  from localstack.utils.strings import long_uid
@@ -246,6 +247,9 @@ class EventsProvider(EventsApi, ServiceLifecycleHook):
246
247
  self._connection_service_store: ConnectionServiceDict = {}
247
248
  self._api_destination_service_store: ApiDestinationServiceDict = {}
248
249
 
250
+ def accept_state_visitor(self, visitor: StateVisitor):
251
+ visitor.visit(events_stores)
252
+
249
253
  def on_before_start(self):
250
254
  JobScheduler.start()
251
255
 
@@ -45,6 +45,7 @@ from localstack.services.events.scheduler import JobScheduler
45
45
  from localstack.services.events.v1.models import EventsStore, events_stores
46
46
  from localstack.services.moto import call_moto
47
47
  from localstack.services.plugins import ServiceLifecycleHook
48
+ from localstack.state import StateVisitor
48
49
  from localstack.utils.aws.arns import event_bus_arn, parse_arn
49
50
  from localstack.utils.aws.client_types import ServicePrincipal
50
51
  from localstack.utils.aws.message_forwarding import send_event_to_target
@@ -83,6 +84,14 @@ class EventsProvider(EventsApi, ServiceLifecycleHook):
83
84
  def on_before_stop(self):
84
85
  JobScheduler.shutdown()
85
86
 
87
+ def accept_state_visitor(self, visitor: StateVisitor):
88
+ from moto.events.models import events_backends
89
+
90
+ from localstack.services.events.v1.models import events_stores
91
+
92
+ visitor.visit(events_backends)
93
+ visitor.visit(events_stores)
94
+
86
95
  @route("/_aws/events/rules/<path:rule_arn>/trigger")
87
96
  def trigger_scheduled_rule(self, request: Request, rule_arn: str):
88
97
  """Developer endpoint to trigger a scheduled rule."""
@@ -94,6 +94,7 @@ from localstack.services.firehose.mappers import (
94
94
  convert_source_config_to_desc,
95
95
  )
96
96
  from localstack.services.firehose.models import FirehoseStore, firehose_stores
97
+ from localstack.state import StateVisitor
97
98
  from localstack.utils.aws.arns import (
98
99
  extract_account_id_from_arn,
99
100
  extract_region_from_arn,
@@ -251,8 +252,12 @@ class FirehoseProvider(FirehoseApi):
251
252
 
252
253
  def __init__(self) -> None:
253
254
  super().__init__()
255
+ # TODO: stop/restart the kinesis listeners when stopping the service / reset the state / restore the state
254
256
  self.kinesis_listeners = {}
255
257
 
258
+ def accept_state_visitor(self, visitor: StateVisitor):
259
+ visitor.visit(firehose_stores)
260
+
256
261
  @staticmethod
257
262
  def get_store(account_id: str, region_name: str) -> FirehoseStore:
258
263
  return firehose_stores[account_id][region_name]
@@ -75,6 +75,7 @@ from localstack.services.iam.resources.policy_simulator import (
75
75
  )
76
76
  from localstack.services.iam.resources.service_linked_roles import SERVICE_LINKED_ROLES
77
77
  from localstack.services.moto import call_moto
78
+ from localstack.state import StateVisitor
78
79
  from localstack.utils.aws.request_context import extract_access_key_id_from_auth_header
79
80
 
80
81
  LOG = logging.getLogger(__name__)
@@ -110,6 +111,9 @@ class IamProvider(IamApi):
110
111
  apply_iam_patches()
111
112
  self.policy_simulator = BasicIAMPolicySimulator()
112
113
 
114
+ def accept_state_visitor(self, visitor: StateVisitor):
115
+ visitor.visit(iam_backends)
116
+
113
117
  @handler("CreateRole", expand=False)
114
118
  def create_role(
115
119
  self, context: RequestContext, request: CreateRoleRequest
@@ -7,7 +7,7 @@ from localstack.packages import InstallTarget, Package
7
7
  from localstack.packages.core import GitHubReleaseInstaller, NodePackageInstaller
8
8
  from localstack.packages.java import JavaInstallerMixin, java_package
9
9
 
10
- _KINESIS_MOCK_VERSION = os.environ.get("KINESIS_MOCK_VERSION") or "0.5.1"
10
+ _KINESIS_MOCK_VERSION = os.environ.get("KINESIS_MOCK_VERSION") or "0.5.2"
11
11
 
12
12
 
13
13
  class KinesisMockEngine(StrEnum):
@@ -20,7 +20,6 @@ from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey
20
20
  from cryptography.hazmat.primitives.asymmetric.padding import PSS, PKCS1v15
21
21
  from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
22
22
  from cryptography.hazmat.primitives.asymmetric.utils import Prehashed
23
- from cryptography.hazmat.primitives.kdf.hkdf import HKDF
24
23
  from cryptography.hazmat.primitives.serialization import load_der_public_key
25
24
 
26
25
  from localstack.aws.api.kms import (
@@ -233,7 +232,10 @@ class KmsCryptoKey:
233
232
 
234
233
  if key_spec.startswith("RSA"):
235
234
  key_size = RSA_CRYPTO_KEY_LENGTHS.get(key_spec)
236
- key = rsa.generate_private_key(public_exponent=65537, key_size=key_size)
235
+ if key_material:
236
+ key = crypto_serialization.load_der_private_key(key_material, password=None)
237
+ else:
238
+ key = rsa.generate_private_key(public_exponent=65537, key_size=key_size)
237
239
  elif key_spec.startswith("ECC"):
238
240
  curve = ECC_CURVES.get(key_spec)
239
241
  if key_material:
@@ -442,17 +444,15 @@ class KmsKey:
442
444
 
443
445
  def derive_shared_secret(self, public_key: bytes) -> bytes:
444
446
  key_spec = self.metadata.get("KeySpec")
445
- match key_spec:
446
- case KeySpec.ECC_NIST_P256 | KeySpec.ECC_SECG_P256K1:
447
- algorithm = hashes.SHA256()
448
- case KeySpec.ECC_NIST_P384:
449
- algorithm = hashes.SHA384()
450
- case KeySpec.ECC_NIST_P521:
451
- algorithm = hashes.SHA512()
452
- case _:
453
- raise InvalidKeyUsageException(
454
- f"{self.metadata['Arn']} key usage is {self.metadata['KeyUsage']} which is not valid for DeriveSharedSecret."
455
- )
447
+ if key_spec not in (
448
+ KeySpec.ECC_NIST_P256,
449
+ KeySpec.ECC_SECG_P256K1,
450
+ KeySpec.ECC_NIST_P384,
451
+ KeySpec.ECC_NIST_P521,
452
+ ):
453
+ raise InvalidKeyUsageException(
454
+ f"{self.metadata['Arn']} key usage is {self.metadata['KeyUsage']} which is not valid for DeriveSharedSecret."
455
+ )
456
456
 
457
457
  # Deserialize public key from DER encoded data to EllipticCurvePublicKey.
458
458
  try:
@@ -460,14 +460,7 @@ class KmsKey:
460
460
  except (UnsupportedAlgorithm, ValueError):
461
461
  raise ValidationException("")
462
462
  shared_secret = self.crypto_key.key.exchange(ec.ECDH(), pub_key)
463
- # Perform shared secret derivation.
464
- return HKDF(
465
- algorithm=algorithm,
466
- salt=None,
467
- info=b"",
468
- length=algorithm.digest_size,
469
- backend=default_backend(),
470
- ).derive(shared_secret)
463
+ return shared_secret
471
464
 
472
465
  # This method gets called when a key is replicated to another region. It's meant to populate the required metadata
473
466
  # fields in a new replica key.
@@ -646,7 +639,8 @@ class KmsKey:
646
639
  # https://docs.aws.amazon.com/kms/latest/APIReference/API_TagResource.html
647
640
  # "To edit a tag, specify an existing tag key and a new tag value."
648
641
  for i, tag in enumerate(tags, start=1):
649
- validate_tag(i, tag)
642
+ if tag.get("TagKey") != TAG_KEY_CUSTOM_KEY_MATERIAL:
643
+ validate_tag(i, tag)
650
644
  self.tags[tag.get("TagKey")] = tag.get("TagValue")
651
645
 
652
646
  def schedule_key_deletion(self, pending_window_in_days: int) -> None:
@@ -138,6 +138,7 @@ from localstack.services.kms.utils import (
138
138
  validate_alias_name,
139
139
  )
140
140
  from localstack.services.plugins import ServiceLifecycleHook
141
+ from localstack.state import StateVisitor
141
142
  from localstack.utils.aws.arns import get_partition, kms_alias_arn, parse_arn
142
143
  from localstack.utils.collections import PaginatedList
143
144
  from localstack.utils.common import select_attributes
@@ -201,6 +202,9 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
201
202
  - VerifyMac
202
203
  """
203
204
 
205
+ def accept_state_visitor(self, visitor: StateVisitor):
206
+ visitor.visit(kms_stores)
207
+
204
208
  #
205
209
  # Helpers
206
210
  #
@@ -14,9 +14,10 @@ function_counter = LabeledCounter(
14
14
  "status",
15
15
  "runtime",
16
16
  "package_type",
17
- # only for operation "invoke"
18
- "invocation_type",
17
+ "invocation_type", # only for operation "invoke", otherwise "n/a"
18
+ "initialization_type",
19
19
  ],
20
+ schema_version=2,
20
21
  )
21
22
 
22
23
 
@@ -38,6 +39,14 @@ class FunctionStatus(StrEnum):
38
39
  invocation_error = "invocation_error"
39
40
 
40
41
 
42
+ class FunctionInitializationType(StrEnum):
43
+ # Maps to the Lambda environment variable AWS_LAMBDA_INITIALIZATION_TYPE
44
+ on_demand = "on-demand"
45
+ lambda_managed_instances = "lambda-managed-instances"
46
+ # Only applies to the operation "invoke" because provisioned concurrency is not configured on "create"
47
+ provisioned_concurrency = "provisioned-concurrency"
48
+
49
+
41
50
  esm_counter = LabeledCounter(namespace=NAMESPACE, name="esm", labels=["source", "status"])
42
51
 
43
52
 
@@ -3,6 +3,8 @@ Everything related to behavior or implicit functionality goes into `lambda_utils
3
3
  """
4
4
 
5
5
  import datetime
6
+ import hashlib
7
+ import json
6
8
  import random
7
9
  import re
8
10
  import string
@@ -62,13 +64,14 @@ DESTINATION_ARN_PATTERN = re.compile(
62
64
  r"^$|arn:(aws[a-zA-Z0-9-]*):([a-zA-Z0-9\-])+:([a-z]{2}(-gov)?-[a-z]+-\d{1})?:(\d{12})?:(.*)"
63
65
  )
64
66
 
67
+ # TODO: what's the difference between AWS_FUNCTION_NAME_REGEX and FUNCTION_NAME_REGEX? Can we unify?
65
68
  AWS_FUNCTION_NAME_REGEX = re.compile(
66
- "^(arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_.]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?$"
69
+ "^(arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:)?(\\d{12}:)?(function:)?([a-zA-Z0-9-_.]+)(:(\\$LATEST(\\.PUBLISHED)?|[a-zA-Z0-9-_]+))?$"
67
70
  )
68
71
 
69
72
  # Pattern for extracting various attributes from a full or partial ARN or just a function name.
70
73
  FUNCTION_NAME_REGEX = re.compile(
71
- r"(arn:(aws[a-zA-Z-]*):lambda:)?((?P<region>[a-z]{2}(-gov)?-[a-z]+-\d{1}):)?(?:(?P<account>\d{12}):)?(function:)?(?P<name>[a-zA-Z0-9-_\.]+)(:(?P<qualifier>\$LATEST|[a-zA-Z0-9-_]+))?"
74
+ r"(arn:(aws[a-zA-Z-]*):lambda:)?((?P<region>[a-z]{2}(-gov)?-[a-z]+-\d{1}):)?(?:(?P<account>\d{12}):)?(function:)?(?P<name>[a-zA-Z0-9-_\.]+)(:(?P<qualifier>\$LATEST(\.PUBLISHED)?|[a-zA-Z0-9-_]+))?"
72
75
  ) # also length 1-170 incl.
73
76
  # Pattern for a lambda function handler
74
77
  HANDLER_REGEX = re.compile(r"[^\s]+")
@@ -86,9 +89,11 @@ SIGNING_JOB_ARN_REGEX = re.compile(
86
89
  SIGNING_PROFILE_VERSION_ARN_REGEX = re.compile(
87
90
  r"arn:(aws[a-zA-Z0-9-]*):([a-zA-Z0-9\-])+:([a-z]{2}(-gov)?-[a-z]+-\d{1})?:(\d{12})?:(.*)"
88
91
  )
89
- # Combined pattern for alias and version based on AWS error using "(|[a-zA-Z0-9$_-]+)"
90
- QUALIFIER_REGEX = re.compile(r"(^[a-zA-Z0-9$_-]+$)")
92
+ # Combined pattern for alias and version based on AWS error using "\\$(LATEST(\\.PUBLISHED)?)|[a-zA-Z0-9-_$]+"
93
+ # This regex is based on the snapshotted validation message, just removing the double \\ before $LATEST
94
+ QUALIFIER_REGEX = re.compile(r"^\$(LATEST(\\.PUBLISHED)?)|[a-zA-Z0-9-_$]+$")
91
95
  # Pattern for a version qualifier
96
+ # TODO: do we need to consider $LATEST.PUBLISHED here?
92
97
  VERSION_REGEX = re.compile(r"^[0-9]+$")
93
98
  # Pattern for an alias qualifier
94
99
  # Rules: https://docs.aws.amazon.com/lambda/latest/dg/API_CreateAlias.html#SSS-CreateAlias-request-Name
@@ -107,36 +112,42 @@ LAMBDA_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%f+0000"
107
112
  # An unordered list of all Lambda CPU architectures supported by LocalStack.
108
113
  ARCHITECTURES = [Architecture.arm64, Architecture.x86_64]
109
114
 
110
- # ARN pattern returned in validation exception messages.
111
- # Some excpetions from AWS return a '\.' in the function name regex
112
- # pattern therefore we can sub this value in when appropriate.
113
- ARN_NAME_PATTERN_VALIDATION_TEMPLATE = "(arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{{2}}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{{1}}:)?(\\d{{12}}:)?(function:)?([a-zA-Z0-9-_{0}]+)(:(\\$LATEST|[a-zA-Z0-9-_]+))?"
115
+ # ARN patterns returned in validation exception messages
116
+ ARN_NAME_PATTERN_GET = r"(arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\d{1}:)?(\d{12}:)?(function:)?([a-zA-Z0-9-_\.]+)(:(\$LATEST(\.PUBLISHED)?|[a-zA-Z0-9-_]+))?"
117
+ ARN_NAME_PATTERN_CREATE = r"(arn:(aws[a-zA-Z-]*)?:lambda:)?((eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\d{1}:)?(\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\$LATEST|[a-zA-Z0-9-_]+))?"
114
118
 
115
119
  # AWS response when invalid ARNs are used in Tag operations.
116
- TAGGABLE_RESOURCE_ARN_PATTERN = "arn:(aws[a-zA-Z-]*):lambda:[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:(function:[a-zA-Z0-9-_]+(:(\\$LATEST|[a-zA-Z0-9-_]+))?|layer:([a-zA-Z0-9-_]+)|code-signing-config:csc-[a-z0-9]{17}|event-source-mapping:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"
120
+ TAGGABLE_RESOURCE_ARN_PATTERN = "arn:(aws[a-zA-Z-]*):lambda:(eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:(function:[a-zA-Z0-9-_]+(:(\\$LATEST|[a-zA-Z0-9-_]+))?|layer:([a-zA-Z0-9-_]+)|code-signing-config:csc-[a-z0-9]{17}|event-source-mapping:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}|capacity-provider:[a-zA-Z0-9-_]+)"
117
121
 
118
122
 
119
123
  def validate_function_name(function_name_or_arn: str, operation_type: str):
120
124
  function_name, *_ = function_locators_from_arn(function_name_or_arn)
121
- arn_name_pattern = ARN_NAME_PATTERN_VALIDATION_TEMPLATE.format("")
125
+ arn_name_pattern = ARN_NAME_PATTERN_CREATE
122
126
  max_length = 170
123
127
 
124
- match operation_type:
125
- case "GetFunction" | "Invoke":
126
- arn_name_pattern = ARN_NAME_PATTERN_VALIDATION_TEMPLATE.format(r"\.")
127
- case "CreateFunction" if function_name == function_name_or_arn: # only a function name
128
+ if operation_type == "GetFunction" or operation_type == "Invoke":
129
+ arn_name_pattern = ARN_NAME_PATTERN_GET
130
+ elif operation_type == "CreateFunction":
131
+ # https://docs.aws.amazon.com/lambda/latest/api/API_CreateFunction.html#lambda-CreateFunction-request-FunctionName
132
+ if function_name == function_name_or_arn: # only a function name
128
133
  max_length = 64
129
- case "CreateFunction" | "DeleteFunction":
134
+ else: # full or partial ARN
130
135
  max_length = 140
136
+ elif operation_type == "DeleteFunction":
137
+ max_length = 140
138
+ arn_name_pattern = ARN_NAME_PATTERN_GET
131
139
 
132
140
  validations = []
133
- if len(function_name_or_arn) > max_length:
134
- constraint = f"Member must have length less than or equal to {max_length}"
141
+ if not AWS_FUNCTION_NAME_REGEX.match(function_name_or_arn) or not function_name:
142
+ constraint = f"Member must satisfy regular expression pattern: {arn_name_pattern}"
135
143
  validation_msg = f"Value '{function_name_or_arn}' at 'functionName' failed to satisfy constraint: {constraint}"
136
144
  validations.append(validation_msg)
145
+ if not operation_type == "CreateFunction":
146
+ # Immediately raises rather than summarizing all validations, except for CreateFunction
147
+ return validations
137
148
 
138
- if not AWS_FUNCTION_NAME_REGEX.match(function_name_or_arn) or not function_name:
139
- constraint = f"Member must satisfy regular expression pattern: {arn_name_pattern}"
149
+ if len(function_name_or_arn) > max_length:
150
+ constraint = f"Member must have length less than or equal to {max_length}"
140
151
  validation_msg = f"Value '{function_name_or_arn}' at 'functionName' failed to satisfy constraint: {constraint}"
141
152
  validations.append(validation_msg)
142
153
 
@@ -154,7 +165,7 @@ def validate_qualifier(qualifier: str):
154
165
  validations.append(validation_msg)
155
166
 
156
167
  if not QUALIFIER_REGEX.match(qualifier):
157
- constraint = "Member must satisfy regular expression pattern: (|[a-zA-Z0-9$_-]+)"
168
+ constraint = "Member must satisfy regular expression pattern: \\$(LATEST(\\.PUBLISHED)?)|[a-zA-Z0-9-_$]+"
158
169
  validation_msg = (
159
170
  f"Value '{qualifier}' at 'qualifier' failed to satisfy constraint: {constraint}"
160
171
  )
@@ -571,6 +582,12 @@ def map_config_out(
571
582
  optional_kwargs["CodeSize"] = 0
572
583
  optional_kwargs["CodeSha256"] = version.config.image.code_sha256
573
584
 
585
+ if version.config.CapacityProviderConfig:
586
+ optional_kwargs["CapacityProviderConfig"] = version.config.CapacityProviderConfig
587
+ data = json.dumps(version.config.CapacityProviderConfig, sort_keys=True).encode("utf-8")
588
+ config_sha_256 = hashlib.sha256(data).hexdigest()
589
+ optional_kwargs["ConfigSha256"] = config_sha_256
590
+
574
591
  # output for an alias qualifier is completely the same except for the returned ARN
575
592
  if alias_name:
576
593
  function_arn = f"{':'.join(version.id.qualified_arn().split(':')[:-1])}:{alias_name}"
@@ -36,7 +36,7 @@ from localstack.services.lambda_.event_source_mapping.senders.sender_utils impor
36
36
  )
37
37
  from localstack.utils.aws.arns import parse_arn, s3_bucket_name
38
38
  from localstack.utils.backoff import ExponentialBackoff
39
- from localstack.utils.batch_policy import Batcher
39
+ from localstack.utils.batching import Batcher
40
40
  from localstack.utils.strings import long_uid
41
41
 
42
42
  LOG = logging.getLogger(__name__)
@@ -88,9 +88,12 @@ class AssignmentService(OtherServiceEndpoint):
88
88
  self, version_manager_id: str, function_version: FunctionVersion
89
89
  ) -> ExecutionEnvironment:
90
90
  LOG.debug("Starting new environment")
91
+ initialization_type = "on-demand"
92
+ if function_version.config.CapacityProviderConfig:
93
+ initialization_type = "lambda-managed-instances"
91
94
  execution_environment = ExecutionEnvironment(
92
95
  function_version=function_version,
93
- initialization_type="on-demand",
96
+ initialization_type=initialization_type,
94
97
  on_timeout=self.on_timeout,
95
98
  version_manager_id=version_manager_id,
96
99
  )
@@ -13,6 +13,7 @@ from botocore.config import Config
13
13
  from localstack import config
14
14
  from localstack.aws.api.lambda_ import InvocationType, TooManyRequestsException
15
15
  from localstack.services.lambda_.analytics import (
16
+ FunctionInitializationType,
16
17
  FunctionOperation,
17
18
  FunctionStatus,
18
19
  function_counter,
@@ -198,22 +199,22 @@ class Poller:
198
199
  def handle_message(self, message: dict) -> None:
199
200
  failure_cause = None
200
201
  qualifier = self.version_manager.function_version.id.qualifier
202
+ function_config = self.version_manager.function_version.config
201
203
  event_invoke_config = self.version_manager.function.event_invoke_configs.get(qualifier)
202
204
  runtime = None
203
205
  status = None
206
+ # TODO: handle initialization_type provisioned-concurrency, which requires enriching invocation_result
207
+ initialization_type = (
208
+ FunctionInitializationType.lambda_managed_instances
209
+ if function_config.CapacityProviderConfig
210
+ else FunctionInitializationType.on_demand
211
+ )
204
212
  try:
205
213
  sqs_invocation = SQSInvocation.decode(message["Body"])
206
214
  invocation = sqs_invocation.invocation
207
215
  try:
208
216
  invocation_result = self.version_manager.invoke(invocation=invocation)
209
- function_config = self.version_manager.function_version.config
210
- function_counter.labels(
211
- operation=FunctionOperation.invoke,
212
- runtime=function_config.runtime or "n/a",
213
- status=FunctionStatus.success,
214
- invocation_type=InvocationType.Event,
215
- package_type=function_config.package_type,
216
- ).increment()
217
+ status = FunctionStatus.success
217
218
  except Exception as e:
218
219
  # Reserved concurrency == 0
219
220
  if self.version_manager.function.reserved_concurrent_executions == 0:
@@ -223,6 +224,7 @@ class Poller:
223
224
  elif not has_enough_time_for_retry(sqs_invocation, event_invoke_config):
224
225
  failure_cause = "EventAgeExceeded"
225
226
  status = FunctionStatus.event_age_exceeded_error
227
+
226
228
  if failure_cause:
227
229
  invocation_result = InvocationResult(
228
230
  is_error=True, request_id=invocation.request_id, payload=None, logs=None
@@ -240,14 +242,14 @@ class Poller:
240
242
  sqs_client.delete_message(
241
243
  QueueUrl=self.event_queue_url, ReceiptHandle=message["ReceiptHandle"]
242
244
  )
243
- # status MUST be set before returning
244
- package_type = self.version_manager.function_version.config.package_type
245
+ assert status, "status MUST be set before returning"
245
246
  function_counter.labels(
246
247
  operation=FunctionOperation.invoke,
247
248
  runtime=runtime or "n/a",
248
249
  status=status,
249
250
  invocation_type=InvocationType.Event,
250
- package_type=package_type,
251
+ package_type=function_config.package_type,
252
+ initialization_type=initialization_type,
251
253
  ).increment()
252
254
 
253
255
  # Good summary blogpost: https://haithai91.medium.com/aws-lambdas-retry-behaviors-edff90e1cf1b
@@ -257,6 +259,8 @@ class Poller:
257
259
  if event_invoke_config and event_invoke_config.maximum_retry_attempts is not None:
258
260
  max_retry_attempts = event_invoke_config.maximum_retry_attempts
259
261
 
262
+ assert invocation_result, "Invocation result MUST exist if we are not returning before"
263
+
260
264
  # An invocation error either leads to a terminal failure or to a scheduled retry
261
265
  if invocation_result.is_error: # invocation error
262
266
  failure_cause = None
@@ -8,6 +8,7 @@ from enum import Enum, auto
8
8
  from threading import RLock, Timer
9
9
 
10
10
  from localstack import config
11
+ from localstack.aws.api.lambda_ import LogFormat
11
12
  from localstack.aws.connect import connect_to
12
13
  from localstack.services.lambda_.invocation.lambda_models import (
13
14
  Credentials,
@@ -149,6 +150,22 @@ class ExecutionEnvironment:
149
150
  # LOCALSTACK_USER conditionally added below
150
151
  }
151
152
  # Conditionally added environment variables
153
+ # Lambda advanced logging controls:
154
+ # https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/
155
+ logging_config = self.function_version.config.logging_config
156
+ if logging_config.get("LogFormat") == LogFormat.JSON:
157
+ env_vars["AWS_LAMBDA_LOG_FORMAT"] = logging_config["LogFormat"]
158
+ # TODO: test this (currently not implemented in LocalStack)
159
+ env_vars["AWS_LAMBDA_LOG_LEVEL"] = logging_config["ApplicationLogLevel"].capitalize()
160
+ # Lambda Managed Instances
161
+ if capacity_provider_config := self.function_version.config.CapacityProviderConfig:
162
+ # Disable dropping privileges for parity
163
+ # TODO: implement mixed permissions (maybe in RIE)
164
+ # env_vars["LOCALSTACK_USER"] = "root"
165
+ env_vars["AWS_LAMBDA_MAX_CONCURRENCY"] = capacity_provider_config[
166
+ "LambdaManagedInstancesCapacityProviderConfig"
167
+ ]["PerExecutionEnvironmentMaxConcurrency"]
168
+ env_vars["TZ"] = ":/etc/localtime"
152
169
  if not config.LAMBDA_DISABLE_AWS_ENDPOINT_URL:
153
170
  env_vars["AWS_ENDPOINT_URL"] = (
154
171
  f"http://{self.runtime_executor.get_endpoint_from_executor()}:{config.GATEWAY_LISTEN[0].port}"
@@ -159,8 +176,6 @@ class ExecutionEnvironment:
159
176
  # Will be overridden by the runtime itself unless it is a provided runtime
160
177
  if self.function_version.config.runtime:
161
178
  env_vars["AWS_EXECUTION_ENV"] = "AWS_Lambda_rapid"
162
- if self.function_version.config.environment:
163
- env_vars.update(self.function_version.config.environment)
164
179
  if config.LAMBDA_INIT_DEBUG:
165
180
  # Disable dropping privileges because it breaks debugging
166
181
  env_vars["LOCALSTACK_USER"] = "root"
@@ -175,6 +190,10 @@ class ExecutionEnvironment:
175
190
  env_vars["LOCALSTACK_MAX_PAYLOAD_SIZE"] = int(
176
191
  config.LAMBDA_LIMITS_MAX_FUNCTION_PAYLOAD_SIZE_BYTES
177
192
  )
193
+
194
+ # Let users overwrite any environment variable at last (if they want so)
195
+ if self.function_version.config.environment:
196
+ env_vars.update(self.function_version.config.environment)
178
197
  return env_vars
179
198
 
180
199
  # Lifecycle methods
@@ -12,7 +12,7 @@ import threading
12
12
  from abc import ABCMeta, abstractmethod
13
13
  from datetime import datetime
14
14
  from pathlib import Path
15
- from typing import IO, Literal, TypedDict
15
+ from typing import IO, Literal, Optional, TypedDict
16
16
 
17
17
  import boto3
18
18
  from botocore.exceptions import ClientError
@@ -22,12 +22,20 @@ from localstack.aws.api import CommonServiceException
22
22
  from localstack.aws.api.lambda_ import (
23
23
  AllowedPublishers,
24
24
  Architecture,
25
+ CapacityProviderArn,
26
+ CapacityProviderConfig,
27
+ CapacityProviderPermissionsConfig,
28
+ CapacityProviderScalingConfig,
29
+ CapacityProviderVpcConfig,
25
30
  CodeSigningPolicies,
26
31
  Cors,
27
32
  DestinationConfig,
33
+ FunctionScalingConfig,
28
34
  FunctionUrlAuthType,
35
+ InstanceRequirements,
29
36
  InvocationType,
30
37
  InvokeMode,
38
+ KMSKeyArn,
31
39
  LastUpdateStatus,
32
40
  LoggingConfig,
33
41
  PackageType,
@@ -38,12 +46,14 @@ from localstack.aws.api.lambda_ import (
38
46
  SnapStartResponse,
39
47
  State,
40
48
  StateReasonCode,
49
+ Timestamp,
41
50
  TracingMode,
42
51
  )
43
52
  from localstack.aws.connect import connect_to
44
53
  from localstack.constants import AWS_REGION_US_EAST_1, INTERNAL_AWS_SECRET_ACCESS_KEY
45
54
  from localstack.services.lambda_.api_utils import qualified_lambda_arn, unqualified_lambda_arn
46
55
  from localstack.utils.archives import unzip
56
+ from localstack.utils.files import chmod_r
47
57
  from localstack.utils.strings import long_uid, short_uid
48
58
 
49
59
  LOG = logging.getLogger(__name__)
@@ -212,6 +222,7 @@ class S3Code(ArchiveCode):
212
222
  with tempfile.NamedTemporaryFile() as file:
213
223
  self._download_archive_to_file(file)
214
224
  unzip(file.name, str(target_path))
225
+ chmod_r(str(target_path), 0o755)
215
226
 
216
227
  def destroy_cached(self) -> None:
217
228
  """
@@ -331,7 +342,7 @@ class VpcConfig:
331
342
  @dataclasses.dataclass(frozen=True)
332
343
  class UpdateStatus:
333
344
  status: LastUpdateStatus | None
334
- code: str | None = None # TODO: probably not a string
345
+ code: str | None = None
335
346
  reason: str | None = None
336
347
 
337
348
 
@@ -568,6 +579,9 @@ class VersionFunctionConfiguration:
568
579
  vpc_config: VpcConfig | None = None
569
580
 
570
581
  logging_config: LoggingConfig = dataclasses.field(default_factory=dict)
582
+ # TODO: why does `CapacityProviderConfig | None = None` fail with on Python 3.13.9:
583
+ # TypeError: unsupported operand type(s) for |: 'NoneType' and 'NoneType'
584
+ CapacityProviderConfig: Optional[CapacityProviderConfig] = None # noqa
571
585
 
572
586
 
573
587
  @dataclasses.dataclass(frozen=True)
@@ -580,6 +594,18 @@ class FunctionVersion:
580
594
  return self.id.qualified_arn()
581
595
 
582
596
 
597
+ @dataclasses.dataclass
598
+ class CapacityProvider:
599
+ CapacityProviderArn: CapacityProviderArn
600
+ # State is determined dynamically
601
+ VpcConfig: CapacityProviderVpcConfig
602
+ PermissionsConfig: CapacityProviderPermissionsConfig
603
+ InstanceRequirements: InstanceRequirements
604
+ CapacityProviderScalingConfig: CapacityProviderScalingConfig
605
+ LastModified: Timestamp
606
+ KmsKeyArn: KMSKeyArn | None = None
607
+
608
+
583
609
  @dataclasses.dataclass
584
610
  class Function:
585
611
  function_name: str
@@ -600,6 +626,9 @@ class Function:
600
626
  provisioned_concurrency_configs: dict[str, ProvisionedConcurrencyConfiguration] = (
601
627
  dataclasses.field(default_factory=dict)
602
628
  )
629
+ function_scaling_configs: dict[str, FunctionScalingConfig] = dataclasses.field(
630
+ default_factory=dict
631
+ )
603
632
 
604
633
  lock: threading.RLock = dataclasses.field(default_factory=threading.RLock)
605
634
  next_version: int = 1