localstack-core 4.11.2.dev14__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.
- localstack/aws/api/ec2/__init__.py +13 -0
- localstack/aws/api/iam/__init__.py +1 -0
- localstack/aws/api/lambda_/__init__.py +616 -0
- localstack/aws/api/logs/__init__.py +188 -0
- localstack/aws/api/opensearch/__init__.py +11 -0
- localstack/aws/api/route53/__init__.py +3 -0
- localstack/aws/api/s3/__init__.py +2 -0
- localstack/aws/api/s3control/__init__.py +19 -0
- localstack/aws/api/secretsmanager/__init__.py +9 -0
- localstack/aws/connect.py +35 -15
- localstack/config.py +8 -0
- localstack/constants.py +3 -0
- localstack/dev/kubernetes/__main__.py +39 -14
- localstack/runtime/analytics.py +11 -0
- localstack/services/acm/provider.py +13 -1
- localstack/services/cloudformation/engine/v2/change_set_model.py +9 -0
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +3 -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/kinesis/packages.py +1 -1
- localstack/services/kms/models.py +6 -2
- localstack/services/lambda_/analytics.py +11 -2
- localstack/services/lambda_/invocation/event_manager.py +15 -11
- localstack/services/lambda_/invocation/lambda_models.py +4 -0
- localstack/services/lambda_/invocation/lambda_service.py +11 -0
- localstack/services/lambda_/provider.py +70 -13
- localstack/services/opensearch/packages.py +34 -20
- 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 +25 -30
- localstack/services/s3/utils.py +46 -1
- localstack/services/s3control/provider.py +6 -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 +13 -0
- localstack/services/sns/provider.py +5 -0
- localstack/services/sns/v2/models.py +3 -0
- localstack/services/sns/v2/provider.py +100 -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/static_analyser/test_state/test_state_analyser.py +193 -107
- localstack/services/stepfunctions/backend/execution.py +4 -5
- localstack/services/stepfunctions/provider.py +21 -14
- 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/utils/aws/client_types.py +2 -4
- localstack/utils/bootstrap.py +2 -2
- localstack/utils/catalog/catalog.py +3 -2
- 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.11.2.dev14.dist-info → localstack_core-4.12.1.dev18.dist-info}/METADATA +6 -6
- {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev18.dist-info}/RECORD +76 -75
- localstack_core-4.12.1.dev18.dist-info/plux.json +1 -0
- localstack_core-4.11.2.dev14.dist-info/plux.json +0 -1
- {localstack_core-4.11.2.dev14.data → localstack_core-4.12.1.dev18.data}/scripts/localstack +0 -0
- {localstack_core-4.11.2.dev14.data → localstack_core-4.12.1.dev18.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.11.2.dev14.data → localstack_core-4.12.1.dev18.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev18.dist-info}/WHEEL +0 -0
- {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev18.dist-info}/entry_points.txt +0 -0
- {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev18.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev18.dist-info}/top_level.txt +0 -0
|
@@ -103,6 +103,9 @@ from localstack.services.cloudformation.engine.v2.change_set_model_transform imp
|
|
|
103
103
|
from localstack.services.cloudformation.engine.v2.change_set_model_validator import (
|
|
104
104
|
ChangeSetModelValidator,
|
|
105
105
|
)
|
|
106
|
+
from localstack.services.cloudformation.engine.v2.change_set_resource_support_checker import (
|
|
107
|
+
ChangeSetResourceSupportChecker,
|
|
108
|
+
)
|
|
106
109
|
from localstack.services.cloudformation.engine.validations import ValidationError
|
|
107
110
|
from localstack.services.cloudformation.provider import (
|
|
108
111
|
ARN_CHANGESET_REGEX,
|
|
@@ -222,6 +225,12 @@ def find_stack_instance(stack_set: StackSet, account: str, region: str) -> Stack
|
|
|
222
225
|
|
|
223
226
|
class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
224
227
|
def on_before_start(self):
|
|
228
|
+
# TODO: make sure to bring `_validate_config` from the base class when removing it
|
|
229
|
+
# as this ensures we have a valid CFN_NO_WAIT_ITERATIONS value
|
|
230
|
+
super().on_before_start()
|
|
231
|
+
self._log_create_issue_info()
|
|
232
|
+
|
|
233
|
+
def _log_create_issue_info(self):
|
|
225
234
|
base = "https://github.com/localstack/localstack/issues/new"
|
|
226
235
|
query_args = {
|
|
227
236
|
"template": "bug-report.yml",
|
|
@@ -417,6 +426,38 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
417
426
|
update_model.node_template.change_type = ChangeType.MODIFIED
|
|
418
427
|
change_set.processed_template = transformed_after_template
|
|
419
428
|
|
|
429
|
+
if not config.CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES:
|
|
430
|
+
support_visitor = ChangeSetResourceSupportChecker()
|
|
431
|
+
support_visitor.visit(change_set.update_model.node_template)
|
|
432
|
+
failure_messages = support_visitor.failure_messages
|
|
433
|
+
if failure_messages:
|
|
434
|
+
reason_suffix = ", ".join(failure_messages)
|
|
435
|
+
status_reason = f"{ChangeSetResourceSupportChecker.TITLE_MESSAGE} {reason_suffix}"
|
|
436
|
+
|
|
437
|
+
change_set.status_reason = status_reason
|
|
438
|
+
change_set.set_change_set_status(ChangeSetStatus.FAILED)
|
|
439
|
+
failure_transitions = {
|
|
440
|
+
ChangeSetType.CREATE: (
|
|
441
|
+
StackStatus.ROLLBACK_IN_PROGRESS,
|
|
442
|
+
StackStatus.CREATE_FAILED,
|
|
443
|
+
),
|
|
444
|
+
ChangeSetType.UPDATE: (
|
|
445
|
+
StackStatus.UPDATE_ROLLBACK_IN_PROGRESS,
|
|
446
|
+
StackStatus.UPDATE_ROLLBACK_FAILED,
|
|
447
|
+
),
|
|
448
|
+
ChangeSetType.IMPORT: (
|
|
449
|
+
StackStatus.IMPORT_ROLLBACK_IN_PROGRESS,
|
|
450
|
+
StackStatus.IMPORT_ROLLBACK_FAILED,
|
|
451
|
+
),
|
|
452
|
+
}
|
|
453
|
+
transitions = failure_transitions.get(change_set.change_set_type)
|
|
454
|
+
if transitions:
|
|
455
|
+
first_status, *remaining_statuses = transitions
|
|
456
|
+
change_set.stack.set_stack_status(first_status, status_reason)
|
|
457
|
+
for status in remaining_statuses:
|
|
458
|
+
change_set.stack.set_stack_status(status)
|
|
459
|
+
return
|
|
460
|
+
|
|
420
461
|
@handler("CreateChangeSet", expand=False)
|
|
421
462
|
def create_change_set(
|
|
422
463
|
self, context: RequestContext, request: CreateChangeSetInput
|
|
@@ -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.
|
|
10
|
+
_KINESIS_MOCK_VERSION = os.environ.get("KINESIS_MOCK_VERSION") or "0.5.2"
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class KinesisMockEngine(StrEnum):
|
|
@@ -232,7 +232,10 @@ class KmsCryptoKey:
|
|
|
232
232
|
|
|
233
233
|
if key_spec.startswith("RSA"):
|
|
234
234
|
key_size = RSA_CRYPTO_KEY_LENGTHS.get(key_spec)
|
|
235
|
-
|
|
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)
|
|
236
239
|
elif key_spec.startswith("ECC"):
|
|
237
240
|
curve = ECC_CURVES.get(key_spec)
|
|
238
241
|
if key_material:
|
|
@@ -636,7 +639,8 @@ class KmsKey:
|
|
|
636
639
|
# https://docs.aws.amazon.com/kms/latest/APIReference/API_TagResource.html
|
|
637
640
|
# "To edit a tag, specify an existing tag key and a new tag value."
|
|
638
641
|
for i, tag in enumerate(tags, start=1):
|
|
639
|
-
|
|
642
|
+
if tag.get("TagKey") != TAG_KEY_CUSTOM_KEY_MATERIAL:
|
|
643
|
+
validate_tag(i, tag)
|
|
640
644
|
self.tags[tag.get("TagKey")] = tag.get("TagValue")
|
|
641
645
|
|
|
642
646
|
def schedule_key_deletion(self, pending_window_in_days: int) -> None:
|
|
@@ -14,9 +14,10 @@ function_counter = LabeledCounter(
|
|
|
14
14
|
"status",
|
|
15
15
|
"runtime",
|
|
16
16
|
"package_type",
|
|
17
|
-
# only for operation "invoke"
|
|
18
|
-
"
|
|
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
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -30,6 +30,7 @@ from localstack.aws.api.lambda_ import (
|
|
|
30
30
|
CodeSigningPolicies,
|
|
31
31
|
Cors,
|
|
32
32
|
DestinationConfig,
|
|
33
|
+
FunctionScalingConfig,
|
|
33
34
|
FunctionUrlAuthType,
|
|
34
35
|
InstanceRequirements,
|
|
35
36
|
InvocationType,
|
|
@@ -625,6 +626,9 @@ class Function:
|
|
|
625
626
|
provisioned_concurrency_configs: dict[str, ProvisionedConcurrencyConfiguration] = (
|
|
626
627
|
dataclasses.field(default_factory=dict)
|
|
627
628
|
)
|
|
629
|
+
function_scaling_configs: dict[str, FunctionScalingConfig] = dataclasses.field(
|
|
630
|
+
default_factory=dict
|
|
631
|
+
)
|
|
628
632
|
|
|
629
633
|
lock: threading.RLock = dataclasses.field(default_factory=threading.RLock)
|
|
630
634
|
next_version: int = 1
|
|
@@ -29,6 +29,7 @@ from localstack.aws.connect import connect_to
|
|
|
29
29
|
from localstack.constants import AWS_REGION_US_EAST_1
|
|
30
30
|
from localstack.services.lambda_ import hooks as lambda_hooks
|
|
31
31
|
from localstack.services.lambda_.analytics import (
|
|
32
|
+
FunctionInitializationType,
|
|
32
33
|
FunctionOperation,
|
|
33
34
|
FunctionStatus,
|
|
34
35
|
function_counter,
|
|
@@ -313,6 +314,12 @@ class LambdaService:
|
|
|
313
314
|
raise ResourceNotFoundException(f"Function not found: {invoked_arn}", Type="User")
|
|
314
315
|
runtime = version.config.runtime or "n/a"
|
|
315
316
|
package_type = version.config.package_type
|
|
317
|
+
# Not considering provisioned concurrency for such early errors
|
|
318
|
+
initialization_type = (
|
|
319
|
+
FunctionInitializationType.lambda_managed_instances
|
|
320
|
+
if version.config.CapacityProviderConfig
|
|
321
|
+
else FunctionInitializationType.on_demand
|
|
322
|
+
)
|
|
316
323
|
if version.config.CapacityProviderConfig and qualifier == "$LATEST":
|
|
317
324
|
if function.versions.get("$LATEST.PUBLISHED"):
|
|
318
325
|
raise InvalidParameterValueException(
|
|
@@ -355,6 +362,7 @@ class LambdaService:
|
|
|
355
362
|
status=status,
|
|
356
363
|
invocation_type=invocation_type,
|
|
357
364
|
package_type=package_type,
|
|
365
|
+
initialization_type=initialization_type,
|
|
358
366
|
).increment()
|
|
359
367
|
raise ResourceConflictException(
|
|
360
368
|
f"The operation cannot be performed at this time. The function is currently in the following state: {state}"
|
|
@@ -373,6 +381,7 @@ class LambdaService:
|
|
|
373
381
|
status=FunctionStatus.invalid_payload_error,
|
|
374
382
|
invocation_type=invocation_type,
|
|
375
383
|
package_type=package_type,
|
|
384
|
+
initialization_type=initialization_type,
|
|
376
385
|
).increment()
|
|
377
386
|
# MAYBE: improve parity of detailed exception message (quite cumbersome)
|
|
378
387
|
raise InvalidRequestContentException(
|
|
@@ -417,12 +426,14 @@ class LambdaService:
|
|
|
417
426
|
if invocation_result.is_error
|
|
418
427
|
else FunctionStatus.success
|
|
419
428
|
)
|
|
429
|
+
# TODO: handle initialization_type provisioned-concurrency, requires enriching invocation_result
|
|
420
430
|
function_counter.labels(
|
|
421
431
|
operation=FunctionOperation.invoke,
|
|
422
432
|
runtime=runtime,
|
|
423
433
|
status=status,
|
|
424
434
|
invocation_type=invocation_type,
|
|
425
435
|
package_type=package_type,
|
|
436
|
+
initialization_type=initialization_type,
|
|
426
437
|
).increment()
|
|
427
438
|
return invocation_result
|
|
428
439
|
|
|
@@ -43,6 +43,7 @@ from localstack.aws.api.lambda_ import (
|
|
|
43
43
|
DeleteFunctionResponse,
|
|
44
44
|
Description,
|
|
45
45
|
DestinationConfig,
|
|
46
|
+
DurableExecutionName,
|
|
46
47
|
EventSourceMappingConfiguration,
|
|
47
48
|
FunctionCodeLocation,
|
|
48
49
|
FunctionConfiguration,
|
|
@@ -157,6 +158,7 @@ from localstack.services.edge import ROUTER
|
|
|
157
158
|
from localstack.services.lambda_ import api_utils
|
|
158
159
|
from localstack.services.lambda_ import hooks as lambda_hooks
|
|
159
160
|
from localstack.services.lambda_.analytics import (
|
|
161
|
+
FunctionInitializationType,
|
|
160
162
|
FunctionOperation,
|
|
161
163
|
FunctionStatus,
|
|
162
164
|
function_counter,
|
|
@@ -255,6 +257,7 @@ from localstack.utils.urls import localstack_host
|
|
|
255
257
|
|
|
256
258
|
LOG = logging.getLogger(__name__)
|
|
257
259
|
|
|
260
|
+
CAPACITY_PROVIDER_ARN_NAME = "arn:aws[a-zA-Z-]*:lambda:(eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:capacity-provider:[a-zA-Z0-9-_]+"
|
|
258
261
|
LAMBDA_DEFAULT_TIMEOUT = 3
|
|
259
262
|
LAMBDA_DEFAULT_MEMORY_SIZE = 128
|
|
260
263
|
|
|
@@ -304,20 +307,26 @@ class LambdaProvider(LambdaApi, ServiceLifecycleHook):
|
|
|
304
307
|
for region_name, state in account_bundle.items():
|
|
305
308
|
for fn in state.functions.values():
|
|
306
309
|
for fn_version in fn.versions.values():
|
|
307
|
-
# restore the "Pending" state for every function version and start it
|
|
308
310
|
try:
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
new_config = dataclasses.replace(fn_version.config, state=new_state)
|
|
315
|
-
new_version = dataclasses.replace(fn_version, config=new_config)
|
|
316
|
-
fn.versions[fn_version.id.qualifier] = new_version
|
|
317
|
-
# TODO: consider skipping this for $LATEST versions of functions with a capacity provider
|
|
318
|
-
self.lambda_service.create_function_version(fn_version).result(
|
|
319
|
-
timeout=5
|
|
311
|
+
# $LATEST is not invokable for Lambda functions with a capacity provider
|
|
312
|
+
# and has a different State (i.e., ActiveNonInvokable)
|
|
313
|
+
is_capacity_provider_latest = (
|
|
314
|
+
fn_version.config.CapacityProviderConfig
|
|
315
|
+
and fn_version.id.qualifier == "$LATEST"
|
|
320
316
|
)
|
|
317
|
+
if not is_capacity_provider_latest:
|
|
318
|
+
# Restore the "Pending" state for the function version and start it
|
|
319
|
+
new_state = VersionState(
|
|
320
|
+
state=State.Pending,
|
|
321
|
+
code=StateReasonCode.Creating,
|
|
322
|
+
reason="The function is being created.",
|
|
323
|
+
)
|
|
324
|
+
new_config = dataclasses.replace(fn_version.config, state=new_state)
|
|
325
|
+
new_version = dataclasses.replace(fn_version, config=new_config)
|
|
326
|
+
fn.versions[fn_version.id.qualifier] = new_version
|
|
327
|
+
self.lambda_service.create_function_version(fn_version).result(
|
|
328
|
+
timeout=5
|
|
329
|
+
)
|
|
321
330
|
except Exception:
|
|
322
331
|
LOG.warning(
|
|
323
332
|
"Failed to restore function version %s",
|
|
@@ -853,6 +862,30 @@ class LambdaProvider(LambdaApi, ServiceLifecycleHook):
|
|
|
853
862
|
)
|
|
854
863
|
visited_layers[layer_arn] = layer_version_arn
|
|
855
864
|
|
|
865
|
+
def _validate_capacity_provider_config(
|
|
866
|
+
self, capacity_provider_config: CapacityProviderConfig, context: RequestContext
|
|
867
|
+
):
|
|
868
|
+
if not capacity_provider_config.get("LambdaManagedInstancesCapacityProviderConfig"):
|
|
869
|
+
raise ValidationException(
|
|
870
|
+
"1 validation error detected: Value null at 'capacityProviderConfig.lambdaManagedInstancesCapacityProviderConfig' failed to satisfy constraint: Member must not be null"
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
capacity_provider_arn = capacity_provider_config.get(
|
|
874
|
+
"LambdaManagedInstancesCapacityProviderConfig", {}
|
|
875
|
+
).get("CapacityProviderArn")
|
|
876
|
+
if not capacity_provider_arn:
|
|
877
|
+
raise ValidationException(
|
|
878
|
+
"1 validation error detected: Value null at 'capacityProviderConfig.lambdaManagedInstancesCapacityProviderConfig.capacityProviderArn' failed to satisfy constraint: Member must not be null"
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
if not re.match(CAPACITY_PROVIDER_ARN_NAME, capacity_provider_arn):
|
|
882
|
+
raise ValidationException(
|
|
883
|
+
f"1 validation error detected: Value '{capacity_provider_arn}' at 'capacityProviderConfig.lambdaManagedInstancesCapacityProviderConfig.capacityProviderArn' failed to satisfy constraint: Member must satisfy regular expression pattern: {CAPACITY_PROVIDER_ARN_NAME}"
|
|
884
|
+
)
|
|
885
|
+
|
|
886
|
+
capacity_provider_name = capacity_provider_arn.split(":")[-1]
|
|
887
|
+
self.get_capacity_provider(context, capacity_provider_name)
|
|
888
|
+
|
|
856
889
|
@staticmethod
|
|
857
890
|
def map_layers(new_layers: list[str]) -> list[LayerVersion]:
|
|
858
891
|
layers = []
|
|
@@ -1029,11 +1062,12 @@ class LambdaProvider(LambdaApi, ServiceLifecycleHook):
|
|
|
1029
1062
|
# Runtime management controls are not available when providing a custom image
|
|
1030
1063
|
runtime_version_config = None
|
|
1031
1064
|
|
|
1032
|
-
# TODO: validations and figure out in which order
|
|
1033
1065
|
capacity_provider_config = None
|
|
1034
1066
|
memory_size = request.get("MemorySize", LAMBDA_DEFAULT_MEMORY_SIZE)
|
|
1035
1067
|
if "CapacityProviderConfig" in request:
|
|
1036
1068
|
capacity_provider_config = request["CapacityProviderConfig"]
|
|
1069
|
+
self._validate_capacity_provider_config(capacity_provider_config, context)
|
|
1070
|
+
|
|
1037
1071
|
default_config = CapacityProviderConfig(
|
|
1038
1072
|
LambdaManagedInstancesCapacityProviderConfig=LambdaManagedInstancesCapacityProviderConfig(
|
|
1039
1073
|
ExecutionEnvironmentMemoryGiBPerVCpu=2.0,
|
|
@@ -1142,12 +1176,18 @@ class LambdaProvider(LambdaApi, ServiceLifecycleHook):
|
|
|
1142
1176
|
)
|
|
1143
1177
|
fn.versions["$LATEST"] = version_post_response or version
|
|
1144
1178
|
state.functions[function_name] = fn
|
|
1179
|
+
initialization_type = (
|
|
1180
|
+
FunctionInitializationType.lambda_managed_instances
|
|
1181
|
+
if capacity_provider_config
|
|
1182
|
+
else FunctionInitializationType.on_demand
|
|
1183
|
+
)
|
|
1145
1184
|
function_counter.labels(
|
|
1146
1185
|
operation=FunctionOperation.create,
|
|
1147
1186
|
runtime=runtime or "n/a",
|
|
1148
1187
|
status=FunctionStatus.success,
|
|
1149
1188
|
invocation_type="n/a",
|
|
1150
1189
|
package_type=package_type,
|
|
1190
|
+
initialization_type=initialization_type,
|
|
1151
1191
|
)
|
|
1152
1192
|
# TODO: consider potential other side effects of not having a function version for $LATEST
|
|
1153
1193
|
# Provisioning happens upon publishing for functions using a capacity provider
|
|
@@ -1363,6 +1403,19 @@ class LambdaProvider(LambdaApi, ServiceLifecycleHook):
|
|
|
1363
1403
|
if new_mode:
|
|
1364
1404
|
replace_kwargs["tracing_config_mode"] = new_mode
|
|
1365
1405
|
|
|
1406
|
+
if "CapacityProviderConfig" in request:
|
|
1407
|
+
if latest_version.config.CapacityProviderConfig and not request[
|
|
1408
|
+
"CapacityProviderConfig"
|
|
1409
|
+
].get("LambdaManagedInstancesCapacityProviderConfig"):
|
|
1410
|
+
raise ValidationException(
|
|
1411
|
+
"1 validation error detected: Value null at 'capacityProviderConfig.lambdaManagedInstancesCapacityProviderConfig' failed to satisfy constraint: Member must not be null"
|
|
1412
|
+
)
|
|
1413
|
+
if not latest_version.config.CapacityProviderConfig:
|
|
1414
|
+
raise InvalidParameterValueException(
|
|
1415
|
+
"CapacityProviderConfig isn't supported for Lambda Default functions.",
|
|
1416
|
+
Type="User",
|
|
1417
|
+
)
|
|
1418
|
+
|
|
1366
1419
|
new_latest_version = dataclasses.replace(
|
|
1367
1420
|
latest_version,
|
|
1368
1421
|
config=dataclasses.replace(
|
|
@@ -1701,6 +1754,7 @@ class LambdaProvider(LambdaApi, ServiceLifecycleHook):
|
|
|
1701
1754
|
invocation_type: InvocationType | None = None,
|
|
1702
1755
|
log_type: LogType | None = None,
|
|
1703
1756
|
client_context: String | None = None,
|
|
1757
|
+
durable_execution_name: DurableExecutionName | None = None,
|
|
1704
1758
|
payload: IO[Blob] | None = None,
|
|
1705
1759
|
qualifier: NumericLatestPublishedOrAliasQualifier | None = None,
|
|
1706
1760
|
tenant_id: TenantId | None = None,
|
|
@@ -2210,6 +2264,9 @@ class LambdaProvider(LambdaApi, ServiceLifecycleHook):
|
|
|
2210
2264
|
raise Exception("unknown version") # TODO: cover via test
|
|
2211
2265
|
elif qualifier == "$LATEST":
|
|
2212
2266
|
pass
|
|
2267
|
+
elif qualifier == "$LATEST.PUBLISHED":
|
|
2268
|
+
if fn.versions.get(qualifier):
|
|
2269
|
+
pass
|
|
2213
2270
|
else:
|
|
2214
2271
|
raise Exception("invalid functionname") # TODO: cover via test
|
|
2215
2272
|
fn_arn = api_utils.qualified_lambda_arn(function_name, qualifier, account, region)
|
|
@@ -107,19 +107,31 @@ class OpensearchPackageInstaller(PackageInstaller):
|
|
|
107
107
|
# setup security based on the version
|
|
108
108
|
self._setup_security(install_dir, parsed_version)
|
|
109
109
|
|
|
110
|
+
# Determine network configuration to use for plugin downloads
|
|
111
|
+
sys_props = {
|
|
112
|
+
**java_system_properties_proxy(),
|
|
113
|
+
**java_system_properties_ssl(
|
|
114
|
+
os.path.join(install_dir, "jdk", "bin", "keytool"),
|
|
115
|
+
{"JAVA_HOME": os.path.join(install_dir, "jdk")},
|
|
116
|
+
),
|
|
117
|
+
}
|
|
118
|
+
java_opts = system_properties_to_cli_args(sys_props)
|
|
119
|
+
|
|
120
|
+
keystore_binary = os.path.join(install_dir, "bin", "opensearch-keystore")
|
|
121
|
+
if os.path.exists(keystore_binary):
|
|
122
|
+
# initialize and create the keystore. Concurrent starts of ES will all try to create it at the same
|
|
123
|
+
# time, and fail with a race condition. Creating once when installing solves the issue without
|
|
124
|
+
# the need to lock the starts
|
|
125
|
+
# Ultimately, each cluster should have its own `config` file and maybe not share the same one
|
|
126
|
+
output = run(
|
|
127
|
+
[keystore_binary, "create"],
|
|
128
|
+
env_vars={"OPENSEARCH_JAVA_OPTS": " ".join(java_opts)},
|
|
129
|
+
)
|
|
130
|
+
LOG.debug("Keystore init output: %s", output)
|
|
131
|
+
|
|
110
132
|
# install other default plugins for opensearch 1.1+
|
|
111
133
|
# https://forum.opensearch.org/t/ingest-attachment-cannot-be-installed/6494/12
|
|
112
134
|
if parsed_version >= "1.1.0":
|
|
113
|
-
# Determine network configuration to use for plugin downloads
|
|
114
|
-
sys_props = {
|
|
115
|
-
**java_system_properties_proxy(),
|
|
116
|
-
**java_system_properties_ssl(
|
|
117
|
-
os.path.join(install_dir, "jdk", "bin", "keytool"),
|
|
118
|
-
{"JAVA_HOME": os.path.join(install_dir, "jdk")},
|
|
119
|
-
),
|
|
120
|
-
}
|
|
121
|
-
java_opts = system_properties_to_cli_args(sys_props)
|
|
122
|
-
|
|
123
135
|
for plugin in OPENSEARCH_PLUGIN_LIST:
|
|
124
136
|
plugin_binary = os.path.join(install_dir, "bin", "opensearch-plugin")
|
|
125
137
|
plugin_dir = os.path.join(install_dir, "plugins", plugin)
|
|
@@ -322,6 +334,18 @@ class ElasticsearchPackageInstaller(PackageInstaller):
|
|
|
322
334
|
if not os.environ.get("IGNORE_ES_DOWNLOAD_ERRORS"):
|
|
323
335
|
raise
|
|
324
336
|
|
|
337
|
+
keystore_binary = os.path.join(install_dir, "bin", "elasticsearch-keystore")
|
|
338
|
+
if os.path.exists(keystore_binary):
|
|
339
|
+
# initialize and create the keystore. Concurrent starts of ES will all try to create it at the same
|
|
340
|
+
# time, and fail with a race condition. Creating once when installing solves the issue without
|
|
341
|
+
# the need to lock the starts
|
|
342
|
+
# Ultimately, each cluster should have its own `config` file and maybe not share the same one
|
|
343
|
+
output = run(
|
|
344
|
+
[keystore_binary, "create"],
|
|
345
|
+
env_vars={"ES_JAVA_OPTS": " ".join(java_opts)},
|
|
346
|
+
)
|
|
347
|
+
LOG.debug("Keystore init output: %s", output)
|
|
348
|
+
|
|
325
349
|
# delete some plugins to free up space
|
|
326
350
|
for plugin in ELASTICSEARCH_DELETE_MODULES:
|
|
327
351
|
module_dir = os.path.join(install_dir, "modules", plugin)
|
|
@@ -341,16 +365,6 @@ class ElasticsearchPackageInstaller(PackageInstaller):
|
|
|
341
365
|
if jvm_options != jvm_options_replaced:
|
|
342
366
|
save_file(jvm_options_file, jvm_options_replaced)
|
|
343
367
|
|
|
344
|
-
# patch JVM options file - replace hardcoded heap size settings
|
|
345
|
-
jvm_options_file = os.path.join(install_dir, "config", "jvm.options")
|
|
346
|
-
if os.path.exists(jvm_options_file):
|
|
347
|
-
jvm_options = load_file(jvm_options_file)
|
|
348
|
-
jvm_options_replaced = re.sub(
|
|
349
|
-
r"(^-Xm[sx][a-zA-Z0-9.]+$)", r"# \1", jvm_options, flags=re.MULTILINE
|
|
350
|
-
)
|
|
351
|
-
if jvm_options != jvm_options_replaced:
|
|
352
|
-
save_file(jvm_options_file, jvm_options_replaced)
|
|
353
|
-
|
|
354
368
|
def _get_install_marker_path(self, install_dir: str) -> str:
|
|
355
369
|
return os.path.join(install_dir, "bin", "elasticsearch")
|
|
356
370
|
|
|
@@ -26,9 +26,16 @@ from localstack.aws.api.route53 import (
|
|
|
26
26
|
from localstack.aws.connect import connect_to
|
|
27
27
|
from localstack.services.moto import call_moto
|
|
28
28
|
from localstack.services.plugins import ServiceLifecycleHook
|
|
29
|
+
from localstack.state import StateVisitor
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
class Route53Provider(Route53Api, ServiceLifecycleHook):
|
|
33
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
34
|
+
from localstack.services.route53.models import route53_stores
|
|
35
|
+
|
|
36
|
+
visitor.visit(route53_backends)
|
|
37
|
+
visitor.visit(route53_stores)
|
|
38
|
+
|
|
32
39
|
def create_hosted_zone(
|
|
33
40
|
self,
|
|
34
41
|
context: RequestContext,
|
|
@@ -100,6 +100,7 @@ from localstack.services.route53resolver.utils import (
|
|
|
100
100
|
validate_mutation_protection,
|
|
101
101
|
validate_priority,
|
|
102
102
|
)
|
|
103
|
+
from localstack.state import StateVisitor
|
|
103
104
|
from localstack.utils.aws import arns
|
|
104
105
|
from localstack.utils.aws.arns import extract_account_id_from_arn, extract_region_from_arn
|
|
105
106
|
from localstack.utils.collections import select_from_typed_dict
|
|
@@ -107,6 +108,10 @@ from localstack.utils.patch import patch
|
|
|
107
108
|
|
|
108
109
|
|
|
109
110
|
class Route53ResolverProvider(Route53ResolverApi):
|
|
111
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
112
|
+
visitor.visit(route53resolver_backends)
|
|
113
|
+
visitor.visit(route53resolver_stores)
|
|
114
|
+
|
|
110
115
|
@staticmethod
|
|
111
116
|
def get_store(account_id: str, region: str) -> Route53ResolverStore:
|
|
112
117
|
return route53resolver_stores[account_id][region]
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from localstack.aws.api.s3 import (
|
|
2
|
+
BucketLocationConstraint,
|
|
2
3
|
ChecksumAlgorithm,
|
|
3
4
|
Grantee,
|
|
4
5
|
Permission,
|
|
@@ -66,6 +67,10 @@ CHECKSUM_ALGORITHMS: list[ChecksumAlgorithm] = [
|
|
|
66
67
|
ChecksumAlgorithm.CRC64NVME,
|
|
67
68
|
]
|
|
68
69
|
|
|
70
|
+
BUCKET_LOCATION_CONSTRAINTS = [constraint.value for constraint in BucketLocationConstraint]
|
|
71
|
+
|
|
72
|
+
EU_WEST_1_LOCATION_CONSTRAINTS = [BucketLocationConstraint.EU, BucketLocationConstraint.eu_west_1]
|
|
73
|
+
|
|
69
74
|
# response header overrides the client may request
|
|
70
75
|
ALLOWED_HEADER_OVERRIDES = {
|
|
71
76
|
"ResponseContentType": "ContentType",
|
|
@@ -51,3 +51,12 @@ class InvalidBucketOwnerAWSAccountID(CommonServiceException):
|
|
|
51
51
|
class TooManyConfigurations(CommonServiceException):
|
|
52
52
|
def __init__(self, message=None) -> None:
|
|
53
53
|
super().__init__("TooManyConfigurations", status_code=400, message=message)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class IllegalLocationConstraintException(CommonServiceException):
|
|
57
|
+
def __init__(self, location_constraint: str):
|
|
58
|
+
super().__init__(
|
|
59
|
+
"IllegalLocationConstraintException",
|
|
60
|
+
status_code=400,
|
|
61
|
+
message=f"The {location_constraint} location constraint is incompatible for the region specific endpoint this request was sent to.",
|
|
62
|
+
)
|
localstack/services/s3/models.py
CHANGED
|
@@ -16,6 +16,7 @@ from localstack.aws.api.s3 import (
|
|
|
16
16
|
BadDigest,
|
|
17
17
|
BucketAccelerateStatus,
|
|
18
18
|
BucketKeyEnabled,
|
|
19
|
+
BucketLocationConstraint,
|
|
19
20
|
BucketName,
|
|
20
21
|
BucketRegion,
|
|
21
22
|
BucketVersioningStatus,
|
|
@@ -76,7 +77,11 @@ from localstack.services.s3.constants import (
|
|
|
76
77
|
S3_UPLOAD_PART_MIN_SIZE,
|
|
77
78
|
)
|
|
78
79
|
from localstack.services.s3.exceptions import InvalidRequest
|
|
79
|
-
from localstack.services.s3.utils import
|
|
80
|
+
from localstack.services.s3.utils import (
|
|
81
|
+
CombinedCrcHash,
|
|
82
|
+
get_s3_checksum,
|
|
83
|
+
rfc_1123_datetime,
|
|
84
|
+
)
|
|
80
85
|
from localstack.services.stores import (
|
|
81
86
|
AccountRegionBundle,
|
|
82
87
|
BaseStore,
|
|
@@ -101,6 +106,7 @@ class S3Bucket:
|
|
|
101
106
|
name: BucketName
|
|
102
107
|
bucket_account_id: AccountId
|
|
103
108
|
bucket_region: BucketRegion
|
|
109
|
+
location_constraint: BucketLocationConstraint | Literal[""]
|
|
104
110
|
creation_date: datetime
|
|
105
111
|
multiparts: dict[MultipartUploadId, "S3Multipart"]
|
|
106
112
|
objects: Union["KeyStore", "VersionedKeyStore"]
|
|
@@ -137,10 +143,12 @@ class S3Bucket:
|
|
|
137
143
|
acl: AccessControlPolicy = None,
|
|
138
144
|
object_ownership: ObjectOwnership = None,
|
|
139
145
|
object_lock_enabled_for_bucket: bool = None,
|
|
146
|
+
location_constraint: BucketLocationConstraint | Literal[""] = "",
|
|
140
147
|
):
|
|
141
148
|
self.name = name
|
|
142
149
|
self.bucket_account_id = account_id
|
|
143
150
|
self.bucket_region = bucket_region
|
|
151
|
+
self.location_constraint = location_constraint
|
|
144
152
|
# If ObjectLock is enabled, it forces the bucket to be versioned as well
|
|
145
153
|
self.versioning_status = None if not object_lock_enabled_for_bucket else "Enabled"
|
|
146
154
|
self.objects = KeyStore() if not object_lock_enabled_for_bucket else VersionedKeyStore()
|