localstack-core 4.10.1.dev7__py3-none-any.whl → 4.10.1.dev42__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- localstack/aws/api/acm/__init__.py +122 -122
- localstack/aws/api/apigateway/__init__.py +560 -559
- localstack/aws/api/cloudcontrol/__init__.py +63 -63
- localstack/aws/api/cloudformation/__init__.py +1040 -969
- localstack/aws/api/cloudwatch/__init__.py +375 -375
- localstack/aws/api/config/__init__.py +784 -786
- localstack/aws/api/dynamodb/__init__.py +753 -759
- localstack/aws/api/dynamodbstreams/__init__.py +74 -74
- localstack/aws/api/ec2/__init__.py +8901 -8818
- localstack/aws/api/es/__init__.py +453 -453
- localstack/aws/api/events/__init__.py +552 -552
- localstack/aws/api/firehose/__init__.py +541 -543
- localstack/aws/api/iam/__init__.py +639 -572
- localstack/aws/api/kinesis/__init__.py +235 -147
- localstack/aws/api/kms/__init__.py +340 -336
- localstack/aws/api/lambda_/__init__.py +574 -573
- localstack/aws/api/logs/__init__.py +676 -675
- localstack/aws/api/opensearch/__init__.py +814 -785
- localstack/aws/api/pipes/__init__.py +336 -336
- localstack/aws/api/redshift/__init__.py +1188 -1166
- localstack/aws/api/resource_groups/__init__.py +175 -175
- localstack/aws/api/resourcegroupstaggingapi/__init__.py +67 -67
- localstack/aws/api/route53/__init__.py +254 -254
- localstack/aws/api/route53resolver/__init__.py +396 -396
- localstack/aws/api/s3/__init__.py +1350 -1349
- localstack/aws/api/s3control/__init__.py +594 -594
- localstack/aws/api/scheduler/__init__.py +118 -118
- localstack/aws/api/secretsmanager/__init__.py +193 -193
- localstack/aws/api/ses/__init__.py +227 -227
- localstack/aws/api/sns/__init__.py +115 -115
- localstack/aws/api/sqs/__init__.py +100 -100
- localstack/aws/api/ssm/__init__.py +1977 -1971
- localstack/aws/api/stepfunctions/__init__.py +323 -323
- localstack/aws/api/sts/__init__.py +90 -66
- localstack/aws/api/support/__init__.py +112 -112
- localstack/aws/api/swf/__init__.py +378 -386
- localstack/aws/api/transcribe/__init__.py +425 -425
- localstack/aws/handlers/service.py +11 -1
- localstack/aws/protocol/parser.py +1 -1
- localstack/aws/scaffold.py +15 -17
- localstack/cli/localstack.py +6 -1
- localstack/dev/kubernetes/__main__.py +38 -3
- localstack/services/apigateway/helpers.py +5 -9
- localstack/services/apigateway/legacy/provider.py +32 -9
- localstack/services/apigateway/patches.py +0 -9
- localstack/services/cloudformation/provider.py +2 -2
- localstack/services/cloudformation/v2/provider.py +6 -6
- localstack/services/kinesis/packages.py +1 -1
- localstack/services/kms/models.py +34 -4
- localstack/services/kms/provider.py +93 -16
- localstack/services/lambda_/api_utils.py +3 -1
- localstack/services/lambda_/packages.py +1 -1
- localstack/services/lambda_/provider.py +1 -1
- localstack/services/lambda_/runtimes.py +8 -3
- localstack/services/logs/provider.py +36 -19
- localstack/services/s3/provider.py +1 -1
- localstack/services/sns/v2/models.py +24 -1
- localstack/services/sns/v2/provider.py +144 -12
- localstack/services/sns/v2/utils.py +8 -0
- localstack/services/sqs/models.py +37 -10
- localstack/testing/snapshots/transformer_utility.py +2 -0
- localstack/testing/testselection/matching.py +0 -1
- localstack/utils/aws/client_types.py +0 -8
- localstack/utils/catalog/catalog_loader.py +111 -3
- localstack/utils/crypto.py +109 -0
- localstack/version.py +2 -2
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.10.1.dev42.dist-info}/METADATA +6 -5
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.10.1.dev42.dist-info}/RECORD +76 -76
- localstack_core-4.10.1.dev42.dist-info/plux.json +1 -0
- localstack_core-4.10.1.dev7.dist-info/plux.json +0 -1
- {localstack_core-4.10.1.dev7.data → localstack_core-4.10.1.dev42.data}/scripts/localstack +0 -0
- {localstack_core-4.10.1.dev7.data → localstack_core-4.10.1.dev42.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.10.1.dev7.data → localstack_core-4.10.1.dev42.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.10.1.dev42.dist-info}/WHEEL +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.10.1.dev42.dist-info}/entry_points.txt +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.10.1.dev42.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.10.1.dev42.dist-info}/top_level.txt +0 -0
|
@@ -4,10 +4,13 @@ import datetime
|
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
6
|
|
|
7
|
+
from cbor2 import loads as cbor2_loads
|
|
7
8
|
from cryptography.exceptions import InvalidTag
|
|
8
9
|
from cryptography.hazmat.backends import default_backend
|
|
9
10
|
from cryptography.hazmat.primitives import hashes, keywrap
|
|
10
11
|
from cryptography.hazmat.primitives.asymmetric import padding
|
|
12
|
+
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
|
|
13
|
+
from cryptography.hazmat.primitives.serialization import load_der_public_key
|
|
11
14
|
|
|
12
15
|
from localstack.aws.api import CommonServiceException, RequestContext, handler
|
|
13
16
|
from localstack.aws.api.kms import (
|
|
@@ -138,6 +141,7 @@ from localstack.services.plugins import ServiceLifecycleHook
|
|
|
138
141
|
from localstack.utils.aws.arns import get_partition, kms_alias_arn, parse_arn
|
|
139
142
|
from localstack.utils.collections import PaginatedList
|
|
140
143
|
from localstack.utils.common import select_attributes
|
|
144
|
+
from localstack.utils.crypto import pkcs7_envelope_encrypt
|
|
141
145
|
from localstack.utils.strings import short_uid, to_bytes, to_str
|
|
142
146
|
|
|
143
147
|
LOG = logging.getLogger(__name__)
|
|
@@ -518,7 +522,12 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
518
522
|
|
|
519
523
|
self.update_primary_key_with_replica_keys(primary_key, replica_key, replica_region)
|
|
520
524
|
|
|
521
|
-
|
|
525
|
+
# CurrentKeyMaterialId is not returned in the ReplicaKeyMetadata. May be due to not being evaluated until
|
|
526
|
+
# the key has been successfully replicated as it does not show up in DescribeKey immediately either.
|
|
527
|
+
replica_key_metadata_response = copy.deepcopy(replica_key.metadata)
|
|
528
|
+
replica_key_metadata_response.pop("CurrentKeyMaterialId", None)
|
|
529
|
+
|
|
530
|
+
return ReplicateKeyResponse(ReplicaKeyMetadata=replica_key_metadata_response)
|
|
522
531
|
|
|
523
532
|
@staticmethod
|
|
524
533
|
# Adds new multi region replica key to the primary key's metadata.
|
|
@@ -1075,6 +1084,25 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1075
1084
|
self._validate_key_for_encryption_decryption(context, key)
|
|
1076
1085
|
self._validate_key_state_not_pending_import(key)
|
|
1077
1086
|
|
|
1087
|
+
# Handle the recipient field. This is used by AWS Nitro to re-encrypt the plaintext to the key specified
|
|
1088
|
+
# by the enclave. Proper support for this will take significant work to figure out how to model enforcing
|
|
1089
|
+
# the attestation measurements; for now, if recipient is specified and has an attestation doc in it including
|
|
1090
|
+
# a public key where it's expected to be, we encrypt to that public key. This at least allows users to use
|
|
1091
|
+
# localstack as a drop-in replacement for AWS when testing without having to skip the secondary decryption
|
|
1092
|
+
# when using localstack.
|
|
1093
|
+
recipient_pubkey = None
|
|
1094
|
+
if recipient:
|
|
1095
|
+
attestation_document = recipient["AttestationDocument"]
|
|
1096
|
+
# We do all of this in a try/catch and warn if it fails so that if users are currently passing a nonsense
|
|
1097
|
+
# value we don't break it for them. In the future we could do a breaking change to require a valid attestation
|
|
1098
|
+
# (or at least one that contains the public key).
|
|
1099
|
+
try:
|
|
1100
|
+
recipient_pubkey = self._extract_attestation_pubkey(attestation_document)
|
|
1101
|
+
except Exception as e:
|
|
1102
|
+
logging.warning(
|
|
1103
|
+
"Unable to extract public key from non-empty attestation document: %s", e
|
|
1104
|
+
)
|
|
1105
|
+
|
|
1078
1106
|
try:
|
|
1079
1107
|
# TODO: Extend the implementation to handle additional encryption/decryption scenarios
|
|
1080
1108
|
# beyond the current support for offline encryption and online decryption using RSA keys if key id exists in
|
|
@@ -1088,20 +1116,27 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1088
1116
|
plaintext = key.decrypt(ciphertext, encryption_context)
|
|
1089
1117
|
except InvalidTag:
|
|
1090
1118
|
raise InvalidCiphertextException()
|
|
1119
|
+
|
|
1091
1120
|
# For compatibility, we return EncryptionAlgorithm values expected from AWS. But LocalStack currently always
|
|
1092
1121
|
# encrypts with symmetric encryption no matter the key settings.
|
|
1093
1122
|
#
|
|
1094
1123
|
# We return a key ARN instead of KeyId despite the name of the parameter, as this is what AWS does and states
|
|
1095
1124
|
# in its docs.
|
|
1096
|
-
# TODO add support for "recipient"
|
|
1097
1125
|
# https://docs.aws.amazon.com/kms/latest/APIReference/API_Decrypt.html#API_Decrypt_RequestSyntax
|
|
1098
1126
|
# TODO add support for "dry_run"
|
|
1099
|
-
|
|
1127
|
+
response = DecryptResponse(
|
|
1100
1128
|
KeyId=key.metadata.get("Arn"),
|
|
1101
|
-
Plaintext=plaintext,
|
|
1102
1129
|
EncryptionAlgorithm=encryption_algorithm,
|
|
1103
1130
|
)
|
|
1104
1131
|
|
|
1132
|
+
# Encrypt to the recipient pubkey if specified. Otherwise, return the actual plaintext
|
|
1133
|
+
if recipient_pubkey:
|
|
1134
|
+
response["CiphertextForRecipient"] = pkcs7_envelope_encrypt(plaintext, recipient_pubkey)
|
|
1135
|
+
else:
|
|
1136
|
+
response["Plaintext"] = plaintext
|
|
1137
|
+
|
|
1138
|
+
return response
|
|
1139
|
+
|
|
1105
1140
|
def get_parameters_for_import(
|
|
1106
1141
|
self,
|
|
1107
1142
|
context: RequestContext,
|
|
@@ -1176,13 +1211,10 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1176
1211
|
# TODO check if there was already a key imported for this kms key
|
|
1177
1212
|
# if so, it has to be identical. We cannot change keys by reimporting after deletion/expiry
|
|
1178
1213
|
key_material = self._decrypt_wrapped_key_material(import_state, encrypted_key_material)
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
key_to_import_material_to.metadata["ExpirationModel"] = (
|
|
1184
|
-
ExpirationModelType.KEY_MATERIAL_EXPIRES
|
|
1185
|
-
)
|
|
1214
|
+
key_material_id = key_to_import_material_to.generate_key_material_id(key_material)
|
|
1215
|
+
key_to_import_material_to.metadata["ExpirationModel"] = (
|
|
1216
|
+
expiration_model or ExpirationModelType.KEY_MATERIAL_EXPIRES
|
|
1217
|
+
)
|
|
1186
1218
|
if (
|
|
1187
1219
|
key_to_import_material_to.metadata["ExpirationModel"]
|
|
1188
1220
|
== ExpirationModelType.KEY_MATERIAL_EXPIRES
|
|
@@ -1191,12 +1223,42 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1191
1223
|
raise ValidationException(
|
|
1192
1224
|
"A validTo date must be set if the ExpirationModel is KEY_MATERIAL_EXPIRES"
|
|
1193
1225
|
)
|
|
1226
|
+
if existing_pending_material := key_to_import_material_to.crypto_key.pending_key_material:
|
|
1227
|
+
pending_key_material_id = key_to_import_material_to.generate_key_material_id(
|
|
1228
|
+
existing_pending_material
|
|
1229
|
+
)
|
|
1230
|
+
raise KMSInvalidStateException(
|
|
1231
|
+
f"New key material (id: {key_material_id}) cannot be imported into KMS key "
|
|
1232
|
+
f"{key_to_import_material_to.metadata['Arn']}, because another key material "
|
|
1233
|
+
f"(id: {pending_key_material_id}) is pending rotation."
|
|
1234
|
+
)
|
|
1235
|
+
|
|
1194
1236
|
# TODO actually set validTo and make the key expire
|
|
1195
1237
|
key_to_import_material_to.metadata["Enabled"] = True
|
|
1196
1238
|
key_to_import_material_to.metadata["KeyState"] = KeyState.Enabled
|
|
1197
1239
|
key_to_import_material_to.crypto_key.load_key_material(key_material)
|
|
1198
1240
|
|
|
1199
|
-
|
|
1241
|
+
# KeyMaterialId / CurrentKeyMaterialId is only exposed for symmetric encryption keys.
|
|
1242
|
+
key_material_id_response = None
|
|
1243
|
+
if key_to_import_material_to.metadata["KeySpec"] == KeySpec.SYMMETRIC_DEFAULT:
|
|
1244
|
+
key_material_id_response = key_to_import_material_to.generate_key_material_id(
|
|
1245
|
+
key_material
|
|
1246
|
+
)
|
|
1247
|
+
|
|
1248
|
+
# If there is no CurrentKeyMaterialId, instantly promote the pending key material to the current.
|
|
1249
|
+
if key_to_import_material_to.metadata.get("CurrentKeyMaterialId") is None:
|
|
1250
|
+
key_to_import_material_to.metadata["CurrentKeyMaterialId"] = (
|
|
1251
|
+
key_material_id_response
|
|
1252
|
+
)
|
|
1253
|
+
key_to_import_material_to.crypto_key.key_material = (
|
|
1254
|
+
key_to_import_material_to.crypto_key.pending_key_material
|
|
1255
|
+
)
|
|
1256
|
+
key_to_import_material_to.crypto_key.pending_key_material = None
|
|
1257
|
+
|
|
1258
|
+
return ImportKeyMaterialResponse(
|
|
1259
|
+
KeyId=key_to_import_material_to.metadata["Arn"],
|
|
1260
|
+
KeyMaterialId=key_material_id_response,
|
|
1261
|
+
)
|
|
1200
1262
|
|
|
1201
1263
|
def delete_imported_key_material(
|
|
1202
1264
|
self,
|
|
@@ -1323,7 +1385,7 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1323
1385
|
key = self._get_kms_key(account_id, region_name, key_id, any_key_state_allowed=True)
|
|
1324
1386
|
|
|
1325
1387
|
response = GetKeyRotationStatusResponse(
|
|
1326
|
-
KeyId=
|
|
1388
|
+
KeyId=key.metadata["Arn"],
|
|
1327
1389
|
KeyRotationEnabled=key.is_key_rotation_enabled,
|
|
1328
1390
|
NextRotationDate=key.next_rotation_date,
|
|
1329
1391
|
)
|
|
@@ -1415,13 +1477,13 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1415
1477
|
|
|
1416
1478
|
if key.metadata["KeySpec"] != KeySpec.SYMMETRIC_DEFAULT:
|
|
1417
1479
|
raise UnsupportedOperationException()
|
|
1418
|
-
|
|
1419
|
-
|
|
1480
|
+
self._validate_key_state_not_pending_import(key)
|
|
1481
|
+
self._validate_external_key_has_pending_material(key)
|
|
1420
1482
|
|
|
1421
1483
|
key.rotate_key_on_demand()
|
|
1422
1484
|
|
|
1423
1485
|
return RotateKeyOnDemandResponse(
|
|
1424
|
-
KeyId=
|
|
1486
|
+
KeyId=key.metadata["Arn"],
|
|
1425
1487
|
)
|
|
1426
1488
|
|
|
1427
1489
|
@handler("TagResource", expand=False)
|
|
@@ -1498,6 +1560,12 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1498
1560
|
if key.metadata["KeyState"] == KeyState.PendingImport:
|
|
1499
1561
|
raise KMSInvalidStateException(f"{key.metadata['Arn']} is pending import.")
|
|
1500
1562
|
|
|
1563
|
+
def _validate_external_key_has_pending_material(self, key: KmsKey):
|
|
1564
|
+
if key.metadata["Origin"] == "EXTERNAL" and key.crypto_key.pending_key_material is None:
|
|
1565
|
+
raise KMSInvalidStateException(
|
|
1566
|
+
f"No available key material pending rotation for the key: {key.metadata['Arn']}."
|
|
1567
|
+
)
|
|
1568
|
+
|
|
1501
1569
|
def _validate_key_for_encryption_decryption(self, context: RequestContext, key: KmsKey):
|
|
1502
1570
|
key_usage = key.metadata["KeyUsage"]
|
|
1503
1571
|
if key_usage != "ENCRYPT_DECRYPT":
|
|
@@ -1559,6 +1627,15 @@ class KmsProvider(KmsApi, ServiceLifecycleHook):
|
|
|
1559
1627
|
f" constraint: [Member must satisfy enum value set: {VALID_OPERATIONS}]"
|
|
1560
1628
|
)
|
|
1561
1629
|
|
|
1630
|
+
def _extract_attestation_pubkey(self, attestation_document: bytes) -> RSAPublicKey:
|
|
1631
|
+
# The attestation document comes as a COSE (CBOR Object Signing and Encryption) object: the CBOR
|
|
1632
|
+
# attestation is signed and then the attestation and signature are again CBOR-encoded. For now
|
|
1633
|
+
# we don't bother validating the signature, though in the future we could.
|
|
1634
|
+
cose_document = cbor2_loads(attestation_document)
|
|
1635
|
+
attestation = cbor2_loads(cose_document[2])
|
|
1636
|
+
public_key_bytes = attestation["public_key"]
|
|
1637
|
+
return load_der_public_key(public_key_bytes)
|
|
1638
|
+
|
|
1562
1639
|
def _decrypt_wrapped_key_material(
|
|
1563
1640
|
self,
|
|
1564
1641
|
import_state: KeyImportState,
|
|
@@ -722,7 +722,9 @@ def validate_layer_runtimes_and_architectures(
|
|
|
722
722
|
validations.append(validation_msg)
|
|
723
723
|
|
|
724
724
|
if compatible_architectures and set(compatible_architectures).difference(ARCHITECTURES):
|
|
725
|
-
constraint =
|
|
725
|
+
constraint = (
|
|
726
|
+
"[Member must satisfy enum value set: [x86_64, arm64], Member must not be null]"
|
|
727
|
+
)
|
|
726
728
|
validation_msg = f"Value '[{', '.join(list(compatible_architectures))}]' at 'compatibleArchitectures' failed to satisfy constraint: Member must satisfy constraint: {constraint}"
|
|
727
729
|
validations.append(validation_msg)
|
|
728
730
|
|
|
@@ -12,7 +12,7 @@ from localstack.utils.platform import get_arch
|
|
|
12
12
|
"""Customized LocalStack version of the AWS Lambda Runtime Interface Emulator (RIE).
|
|
13
13
|
https://github.com/localstack/lambda-runtime-init/blob/localstack/README-LOCALSTACK.md
|
|
14
14
|
"""
|
|
15
|
-
LAMBDA_RUNTIME_DEFAULT_VERSION = "v0.1.
|
|
15
|
+
LAMBDA_RUNTIME_DEFAULT_VERSION = "v0.1.37-pre"
|
|
16
16
|
LAMBDA_RUNTIME_VERSION = config.LAMBDA_INIT_RELEASE_VERSION or LAMBDA_RUNTIME_DEFAULT_VERSION
|
|
17
17
|
LAMBDA_RUNTIME_INIT_URL = "https://github.com/localstack/lambda-runtime-init/releases/download/{version}/aws-lambda-rie-{arch}"
|
|
18
18
|
|
|
@@ -3728,7 +3728,7 @@ class LambdaProvider(LambdaApi, ServiceLifecycleHook):
|
|
|
3728
3728
|
if not layer_version:
|
|
3729
3729
|
raise ValidationException(
|
|
3730
3730
|
f"1 validation error detected: Value '{arn}' at 'arn' failed to satisfy constraint: Member must satisfy regular expression pattern: "
|
|
3731
|
-
+ "(arn:(aws[a-zA-Z-]*)?:lambda:[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:layer:[a-zA-Z0-9-_]+:[0-9]+)|(arn:[a-zA-Z0-9-]+:lambda:::awslayer:[a-zA-Z0-9-_]+)"
|
|
3731
|
+
+ "(arn:(aws[a-zA-Z-]*)?:lambda:(eusc-)?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}:\\d{12}:layer:[a-zA-Z0-9-_]+:[0-9]+)|(arn:[a-zA-Z0-9-]+:lambda:::awslayer:[a-zA-Z0-9-_]+)"
|
|
3732
3732
|
)
|
|
3733
3733
|
|
|
3734
3734
|
store = lambda_stores[account_id][region_name]
|
|
@@ -23,7 +23,7 @@ from localstack.aws.api.lambda_ import Runtime
|
|
|
23
23
|
# 5. Run the unit test to check the runtime setup:
|
|
24
24
|
# tests.unit.services.lambda_.test_api_utils.TestApiUtils.test_check_runtime
|
|
25
25
|
# 6. Review special tests including:
|
|
26
|
-
# a) [
|
|
26
|
+
# a) [pro] tests.aws.services.lambda_.test_lambda_endpoint_injection
|
|
27
27
|
# 7. Before merging, run the ext integration tests to cover transparent endpoint injection testing.
|
|
28
28
|
# 8. Add the new runtime to the K8 image build: https://github.com/localstack/lambda-images
|
|
29
29
|
# 9. Inform the web team to update the resource browser (consider offering an endpoint in the future)
|
|
@@ -40,6 +40,7 @@ IMAGE_MAPPING: dict[Runtime, str] = {
|
|
|
40
40
|
Runtime.nodejs16_x: "nodejs:16",
|
|
41
41
|
Runtime.nodejs14_x: "nodejs:14", # deprecated Dec 4, 2023 => Jan 9, 2024 => Feb 8, 2024
|
|
42
42
|
Runtime.nodejs12_x: "nodejs:12", # deprecated Mar 31, 2023 => Mar 31, 2023 => Apr 30, 2023
|
|
43
|
+
Runtime.python3_14: "python:3.14",
|
|
43
44
|
Runtime.python3_13: "python:3.13",
|
|
44
45
|
Runtime.python3_12: "python:3.12",
|
|
45
46
|
Runtime.python3_11: "python:3.11",
|
|
@@ -47,6 +48,7 @@ IMAGE_MAPPING: dict[Runtime, str] = {
|
|
|
47
48
|
Runtime.python3_9: "python:3.9",
|
|
48
49
|
Runtime.python3_8: "python:3.8",
|
|
49
50
|
Runtime.python3_7: "python:3.7", # deprecated Dec 4, 2023 => Jan 9, 2024 => Feb 8, 2024
|
|
51
|
+
Runtime.java25: "java:25",
|
|
50
52
|
Runtime.java21: "java:21",
|
|
51
53
|
Runtime.java17: "java:17",
|
|
52
54
|
Runtime.java11: "java:11",
|
|
@@ -116,6 +118,7 @@ RUNTIMES_AGGREGATED = {
|
|
|
116
118
|
Runtime.nodejs16_x,
|
|
117
119
|
],
|
|
118
120
|
"python": [
|
|
121
|
+
Runtime.python3_14,
|
|
119
122
|
Runtime.python3_13,
|
|
120
123
|
Runtime.python3_12,
|
|
121
124
|
Runtime.python3_11,
|
|
@@ -124,6 +127,7 @@ RUNTIMES_AGGREGATED = {
|
|
|
124
127
|
Runtime.python3_8,
|
|
125
128
|
],
|
|
126
129
|
"java": [
|
|
130
|
+
Runtime.java25,
|
|
127
131
|
Runtime.java21,
|
|
128
132
|
Runtime.java17,
|
|
129
133
|
Runtime.java11,
|
|
@@ -155,12 +159,13 @@ SNAP_START_SUPPORTED_RUNTIMES = [
|
|
|
155
159
|
Runtime.java11,
|
|
156
160
|
Runtime.java17,
|
|
157
161
|
Runtime.java21,
|
|
162
|
+
Runtime.java25,
|
|
158
163
|
Runtime.python3_12,
|
|
159
164
|
Runtime.python3_13,
|
|
160
165
|
Runtime.dotnet8,
|
|
161
166
|
]
|
|
162
167
|
|
|
163
168
|
# An ordered list of all Lambda runtimes considered valid by AWS. Matching snapshots in test_create_lambda_exceptions
|
|
164
|
-
VALID_RUNTIMES: str = "[nodejs20.x, provided.al2023, python3.12, python3.13, nodejs22.x, java17, nodejs16.x, dotnet8, python3.10, java11, python3.11, dotnet6, java21, nodejs18.x, provided.al2, ruby3.3, ruby3.4, java8.al2, ruby3.2, python3.8, python3.9]"
|
|
169
|
+
VALID_RUNTIMES: str = "[nodejs20.x, python3.14, provided.al2023, python3.12, python3.13, nodejs22.x, java17, nodejs16.x, java25, dotnet8, python3.10, java11, python3.11, dotnet6, java21, nodejs18.x, provided.al2, ruby3.3, ruby3.4, java8.al2, ruby3.2, python3.8, python3.9]"
|
|
165
170
|
# An ordered list of all Lambda runtimes for layers considered valid by AWS. Matching snapshots in test_layer_exceptions
|
|
166
|
-
VALID_LAYER_RUNTIMES: str = "[ruby2.6, dotnetcore1.0, python3.7, nodejs8.10, nasa, ruby2.7, python2.7-greengrass, dotnetcore2.0, python3.8, java21, dotnet6, dotnetcore2.1, python3.9, java11, nodejs6.10, provided, dotnetcore3.1, dotnet8, java25, java17, nodejs, nodejs4.3, java8.al2, go1.x, dotnet10, nodejs20.x, go1.9, byol, nodejs10.x, provided.al2023, nodejs22.x, python3.10, java8, nodejs12.x, python3.11, nodejs24.x, nodejs8.x, python3.12, nodejs14.x, nodejs8.9, python3.13, python3.14, nodejs16.x, provided.al2, nodejs4.3-edge, nodejs18.x, ruby3.2, python3.4, ruby3.3, ruby3.4, ruby2.5, python3.6, python2.7]"
|
|
171
|
+
VALID_LAYER_RUNTIMES: str = "[ruby3.5, ruby2.6, dotnetcore1.0, python3.7, nodejs8.10, nasa, ruby2.7, python2.7-greengrass, dotnetcore2.0, python3.8, java21, dotnet6, dotnetcore2.1, python3.9, java11, nodejs6.10, provided, dotnetcore3.1, dotnet8, java25, java17, nodejs, nodejs4.3, java8.al2, go1.x, dotnet10, nodejs20.x, go1.9, byol, nodejs10.x, provided.al2023, nodejs22.x, python3.10, java8, nodejs12.x, python3.11, nodejs24.x, nodejs8.x, python3.12, nodejs14.x, nodejs8.9, nodejs26.x, python3.13, python3.14, nodejs16.x, python3.15, provided.al2, nodejs4.3-edge, nodejs18.x, ruby3.2, python3.4, ruby3.3, ruby3.4, ruby2.5, python3.6, python2.7]"
|
|
@@ -22,10 +22,13 @@ from localstack.aws.api.logs import (
|
|
|
22
22
|
InputLogEvents,
|
|
23
23
|
InvalidParameterException,
|
|
24
24
|
KmsKeyId,
|
|
25
|
+
ListLogGroupsRequest,
|
|
26
|
+
ListLogGroupsResponse,
|
|
25
27
|
ListTagsForResourceResponse,
|
|
26
28
|
ListTagsLogGroupResponse,
|
|
27
29
|
LogGroupClass,
|
|
28
30
|
LogGroupName,
|
|
31
|
+
LogGroupSummary,
|
|
29
32
|
LogsApi,
|
|
30
33
|
LogStreamName,
|
|
31
34
|
PutLogEventsResponse,
|
|
@@ -43,7 +46,7 @@ from localstack.services.plugins import ServiceLifecycleHook
|
|
|
43
46
|
from localstack.utils.aws import arns
|
|
44
47
|
from localstack.utils.aws.client_types import ServicePrincipal
|
|
45
48
|
from localstack.utils.bootstrap import is_api_enabled
|
|
46
|
-
from localstack.utils.
|
|
49
|
+
from localstack.utils.numbers import is_number
|
|
47
50
|
from localstack.utils.patch import patch
|
|
48
51
|
|
|
49
52
|
LOG = logging.getLogger(__name__)
|
|
@@ -60,8 +63,8 @@ class LogsProvider(LogsApi, ServiceLifecycleHook):
|
|
|
60
63
|
log_group_name: LogGroupName,
|
|
61
64
|
log_stream_name: LogStreamName,
|
|
62
65
|
log_events: InputLogEvents,
|
|
63
|
-
sequence_token: SequenceToken = None,
|
|
64
|
-
entity: Entity = None,
|
|
66
|
+
sequence_token: SequenceToken | None = None,
|
|
67
|
+
entity: Entity | None = None,
|
|
65
68
|
**kwargs,
|
|
66
69
|
) -> PutLogEventsResponse:
|
|
67
70
|
logs_backend = get_moto_logs_backend(context.account_id, context.region)
|
|
@@ -97,33 +100,32 @@ class LogsProvider(LogsApi, ServiceLifecycleHook):
|
|
|
97
100
|
) -> DescribeLogGroupsResponse:
|
|
98
101
|
region_backend = get_moto_logs_backend(context.account_id, context.region)
|
|
99
102
|
|
|
100
|
-
prefix: str = request.get("logGroupNamePrefix", "")
|
|
101
|
-
pattern: str = request.get("logGroupNamePattern", "")
|
|
103
|
+
prefix: str | None = request.get("logGroupNamePrefix", "")
|
|
104
|
+
pattern: str | None = request.get("logGroupNamePattern", "")
|
|
102
105
|
|
|
103
106
|
if pattern and prefix:
|
|
104
107
|
raise InvalidParameterException(
|
|
105
108
|
"LogGroup name prefix and LogGroup name pattern are mutually exclusive parameters."
|
|
106
109
|
)
|
|
107
110
|
|
|
108
|
-
|
|
111
|
+
moto_groups = copy.deepcopy(dict(region_backend.groups)).values()
|
|
109
112
|
|
|
110
113
|
groups = [
|
|
111
|
-
group.to_describe_dict()
|
|
112
|
-
for
|
|
114
|
+
{"logGroupClass": LogGroupClass.STANDARD} | group.to_describe_dict()
|
|
115
|
+
for group in sorted(moto_groups, key=lambda g: g.name)
|
|
113
116
|
if not (prefix or pattern)
|
|
114
|
-
or (prefix and name.startswith(prefix))
|
|
115
|
-
or (pattern and pattern in name)
|
|
117
|
+
or (prefix and group.name.startswith(prefix))
|
|
118
|
+
or (pattern and pattern in group.name)
|
|
116
119
|
]
|
|
117
120
|
|
|
118
|
-
groups = sorted(groups, key=lambda x: x["logGroupName"])
|
|
119
121
|
return DescribeLogGroupsResponse(logGroups=groups)
|
|
120
122
|
|
|
121
123
|
@handler("DescribeLogStreams", expand=False)
|
|
122
124
|
def describe_log_streams(
|
|
123
125
|
self, context: RequestContext, request: DescribeLogStreamsRequest
|
|
124
126
|
) -> DescribeLogStreamsResponse:
|
|
125
|
-
log_group_name: str = request.get("logGroupName")
|
|
126
|
-
log_group_identifier: str = request.get("logGroupIdentifier")
|
|
127
|
+
log_group_name: str | None = request.get("logGroupName")
|
|
128
|
+
log_group_identifier: str | None = request.get("logGroupIdentifier")
|
|
127
129
|
|
|
128
130
|
if log_group_identifier and log_group_name:
|
|
129
131
|
raise CommonServiceException(
|
|
@@ -138,13 +140,29 @@ class LogsProvider(LogsApi, ServiceLifecycleHook):
|
|
|
138
140
|
|
|
139
141
|
return moto.call_moto_with_request(context, request_copy)
|
|
140
142
|
|
|
143
|
+
@handler("ListLogGroups", expand=False)
|
|
144
|
+
def list_log_groups(
|
|
145
|
+
self, context: RequestContext, request: ListLogGroupsRequest
|
|
146
|
+
) -> ListLogGroupsResponse:
|
|
147
|
+
pattern: str | None = request.get("logGroupNamePattern")
|
|
148
|
+
region_backend: LogsBackend = get_moto_logs_backend(context.account_id, context.region)
|
|
149
|
+
moto_groups = copy.deepcopy(region_backend.groups).values()
|
|
150
|
+
groups = [
|
|
151
|
+
LogGroupSummary(
|
|
152
|
+
logGroupName=group.name, logGroupArn=group.arn, logGroupClass=LogGroupClass.STANDARD
|
|
153
|
+
)
|
|
154
|
+
for group in sorted(moto_groups, key=lambda g: g.name)
|
|
155
|
+
if not pattern or pattern in group.name
|
|
156
|
+
]
|
|
157
|
+
return ListLogGroupsResponse(logGroups=groups)
|
|
158
|
+
|
|
141
159
|
def create_log_group(
|
|
142
160
|
self,
|
|
143
161
|
context: RequestContext,
|
|
144
162
|
log_group_name: LogGroupName,
|
|
145
|
-
kms_key_id: KmsKeyId = None,
|
|
146
|
-
tags: Tags = None,
|
|
147
|
-
log_group_class: LogGroupClass = None,
|
|
163
|
+
kms_key_id: KmsKeyId | None = None,
|
|
164
|
+
tags: Tags | None = None,
|
|
165
|
+
log_group_class: LogGroupClass | None = None,
|
|
148
166
|
**kwargs,
|
|
149
167
|
) -> None:
|
|
150
168
|
call_moto(context)
|
|
@@ -442,10 +460,9 @@ def moto_to_describe_dict(target, self):
|
|
|
442
460
|
# reported race condition in https://github.com/localstack/localstack/issues/8011
|
|
443
461
|
# making copy of "streams" dict here to avoid issues while summing up storedBytes
|
|
444
462
|
copy_streams = copy.deepcopy(self.streams)
|
|
445
|
-
# parity tests shows that the arn ends with ":*"
|
|
446
|
-
arn = self.arn if self.arn.endswith(":*") else f"{self.arn}:*"
|
|
447
463
|
log_group = {
|
|
448
|
-
"arn": arn,
|
|
464
|
+
"arn": f"{self.arn}:*",
|
|
465
|
+
"logGroupArn": self.arn,
|
|
449
466
|
"creationTime": self.creation_time,
|
|
450
467
|
"logGroupName": self.name,
|
|
451
468
|
"metricFilterCount": 0,
|
|
@@ -502,7 +502,7 @@ class S3Provider(S3Api, ServiceLifecycleHook):
|
|
|
502
502
|
raise MalformedXML()
|
|
503
503
|
|
|
504
504
|
if context.region == AWS_REGION_US_EAST_1:
|
|
505
|
-
if bucket_region
|
|
505
|
+
if bucket_region in ("us-east-1", "aws-global"):
|
|
506
506
|
raise InvalidLocationConstraint(
|
|
507
507
|
"The specified location-constraint is not valid",
|
|
508
508
|
LocationConstraint=bucket_region,
|
|
@@ -5,6 +5,7 @@ from enum import StrEnum
|
|
|
5
5
|
from typing import Literal, TypedDict
|
|
6
6
|
|
|
7
7
|
from localstack.aws.api.sns import (
|
|
8
|
+
Endpoint,
|
|
8
9
|
MessageAttributeMap,
|
|
9
10
|
PlatformApplication,
|
|
10
11
|
PublishBatchRequestEntry,
|
|
@@ -39,6 +40,12 @@ SnsApplicationPlatforms = Literal[
|
|
|
39
40
|
]
|
|
40
41
|
|
|
41
42
|
|
|
43
|
+
class EndpointAttributeNames(StrEnum):
|
|
44
|
+
CUSTOM_USER_DATA = "CustomUserData"
|
|
45
|
+
Token = "Token"
|
|
46
|
+
ENABLED = "Enabled"
|
|
47
|
+
|
|
48
|
+
|
|
42
49
|
SMS_ATTRIBUTE_NAMES = [
|
|
43
50
|
"DeliveryStatusIAMRole",
|
|
44
51
|
"DeliveryStatusSuccessSamplingRate",
|
|
@@ -143,6 +150,19 @@ class SnsMessage:
|
|
|
143
150
|
)
|
|
144
151
|
|
|
145
152
|
|
|
153
|
+
@dataclass
|
|
154
|
+
class PlatformEndpoint:
|
|
155
|
+
platform_application_arn: str
|
|
156
|
+
platform_endpoint: Endpoint
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@dataclass
|
|
160
|
+
class PlatformApplicationDetails:
|
|
161
|
+
platform_application: PlatformApplication
|
|
162
|
+
# maps all Endpoints of the PlatformApplication, from their Token to their ARN
|
|
163
|
+
platform_endpoints: dict[str, str]
|
|
164
|
+
|
|
165
|
+
|
|
146
166
|
class SnsStore(BaseStore):
|
|
147
167
|
topics: dict[str, Topic] = LocalAttribute(default=dict)
|
|
148
168
|
|
|
@@ -156,7 +176,10 @@ class SnsStore(BaseStore):
|
|
|
156
176
|
subscription_tokens: dict[str, str] = LocalAttribute(default=dict)
|
|
157
177
|
|
|
158
178
|
# maps platform application arns to platform applications
|
|
159
|
-
platform_applications: dict[str,
|
|
179
|
+
platform_applications: dict[str, PlatformApplicationDetails] = LocalAttribute(default=dict)
|
|
180
|
+
|
|
181
|
+
# maps endpoint arns to platform endpoints
|
|
182
|
+
platform_endpoints: dict[str, PlatformEndpoint] = LocalAttribute(default=dict)
|
|
160
183
|
|
|
161
184
|
# topic/subscription independent default values for sending sms messages
|
|
162
185
|
sms_attributes: dict[str, str] = LocalAttribute(default=dict)
|