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.
Files changed (77) hide show
  1. localstack/aws/api/acm/__init__.py +122 -122
  2. localstack/aws/api/apigateway/__init__.py +560 -559
  3. localstack/aws/api/cloudcontrol/__init__.py +63 -63
  4. localstack/aws/api/cloudformation/__init__.py +1040 -969
  5. localstack/aws/api/cloudwatch/__init__.py +375 -375
  6. localstack/aws/api/config/__init__.py +784 -786
  7. localstack/aws/api/dynamodb/__init__.py +753 -759
  8. localstack/aws/api/dynamodbstreams/__init__.py +74 -74
  9. localstack/aws/api/ec2/__init__.py +8901 -8818
  10. localstack/aws/api/es/__init__.py +453 -453
  11. localstack/aws/api/events/__init__.py +552 -552
  12. localstack/aws/api/firehose/__init__.py +541 -543
  13. localstack/aws/api/iam/__init__.py +639 -572
  14. localstack/aws/api/kinesis/__init__.py +235 -147
  15. localstack/aws/api/kms/__init__.py +340 -336
  16. localstack/aws/api/lambda_/__init__.py +574 -573
  17. localstack/aws/api/logs/__init__.py +676 -675
  18. localstack/aws/api/opensearch/__init__.py +814 -785
  19. localstack/aws/api/pipes/__init__.py +336 -336
  20. localstack/aws/api/redshift/__init__.py +1188 -1166
  21. localstack/aws/api/resource_groups/__init__.py +175 -175
  22. localstack/aws/api/resourcegroupstaggingapi/__init__.py +67 -67
  23. localstack/aws/api/route53/__init__.py +254 -254
  24. localstack/aws/api/route53resolver/__init__.py +396 -396
  25. localstack/aws/api/s3/__init__.py +1350 -1349
  26. localstack/aws/api/s3control/__init__.py +594 -594
  27. localstack/aws/api/scheduler/__init__.py +118 -118
  28. localstack/aws/api/secretsmanager/__init__.py +193 -193
  29. localstack/aws/api/ses/__init__.py +227 -227
  30. localstack/aws/api/sns/__init__.py +115 -115
  31. localstack/aws/api/sqs/__init__.py +100 -100
  32. localstack/aws/api/ssm/__init__.py +1977 -1971
  33. localstack/aws/api/stepfunctions/__init__.py +323 -323
  34. localstack/aws/api/sts/__init__.py +90 -66
  35. localstack/aws/api/support/__init__.py +112 -112
  36. localstack/aws/api/swf/__init__.py +378 -386
  37. localstack/aws/api/transcribe/__init__.py +425 -425
  38. localstack/aws/handlers/service.py +11 -1
  39. localstack/aws/protocol/parser.py +1 -1
  40. localstack/aws/scaffold.py +15 -17
  41. localstack/cli/localstack.py +6 -1
  42. localstack/dev/kubernetes/__main__.py +38 -3
  43. localstack/services/apigateway/helpers.py +5 -9
  44. localstack/services/apigateway/legacy/provider.py +32 -9
  45. localstack/services/apigateway/patches.py +0 -9
  46. localstack/services/cloudformation/provider.py +2 -2
  47. localstack/services/cloudformation/v2/provider.py +6 -6
  48. localstack/services/kinesis/packages.py +1 -1
  49. localstack/services/kms/models.py +34 -4
  50. localstack/services/kms/provider.py +93 -16
  51. localstack/services/lambda_/api_utils.py +3 -1
  52. localstack/services/lambda_/packages.py +1 -1
  53. localstack/services/lambda_/provider.py +1 -1
  54. localstack/services/lambda_/runtimes.py +8 -3
  55. localstack/services/logs/provider.py +36 -19
  56. localstack/services/s3/provider.py +1 -1
  57. localstack/services/sns/v2/models.py +24 -1
  58. localstack/services/sns/v2/provider.py +144 -12
  59. localstack/services/sns/v2/utils.py +8 -0
  60. localstack/services/sqs/models.py +37 -10
  61. localstack/testing/snapshots/transformer_utility.py +2 -0
  62. localstack/testing/testselection/matching.py +0 -1
  63. localstack/utils/aws/client_types.py +0 -8
  64. localstack/utils/catalog/catalog_loader.py +111 -3
  65. localstack/utils/crypto.py +109 -0
  66. localstack/version.py +2 -2
  67. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.10.1.dev42.dist-info}/METADATA +6 -5
  68. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.10.1.dev42.dist-info}/RECORD +76 -76
  69. localstack_core-4.10.1.dev42.dist-info/plux.json +1 -0
  70. localstack_core-4.10.1.dev7.dist-info/plux.json +0 -1
  71. {localstack_core-4.10.1.dev7.data → localstack_core-4.10.1.dev42.data}/scripts/localstack +0 -0
  72. {localstack_core-4.10.1.dev7.data → localstack_core-4.10.1.dev42.data}/scripts/localstack-supervisor +0 -0
  73. {localstack_core-4.10.1.dev7.data → localstack_core-4.10.1.dev42.data}/scripts/localstack.bat +0 -0
  74. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.10.1.dev42.dist-info}/WHEEL +0 -0
  75. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.10.1.dev42.dist-info}/entry_points.txt +0 -0
  76. {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.10.1.dev42.dist-info}/licenses/LICENSE.txt +0 -0
  77. {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
- return ReplicateKeyResponse(ReplicaKeyMetadata=replica_key.metadata)
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
- return DecryptResponse(
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
- if expiration_model:
1181
- key_to_import_material_to.metadata["ExpirationModel"] = expiration_model
1182
- else:
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
- return ImportKeyMaterialResponse()
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=key_id,
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
- if key.metadata["Origin"] == OriginType.EXTERNAL:
1419
- raise NotImplementedError("Rotation of imported keys is not supported yet.")
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=key_id,
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 = "[Member must satisfy enum value set: [x86_64, arm64]]"
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.36-pre"
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) [ext] tests.aws.services.lambda_.test_lambda_endpoint_injection
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.common import is_number
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
- copy_groups = copy.deepcopy(dict(region_backend.groups))
111
+ moto_groups = copy.deepcopy(dict(region_backend.groups)).values()
109
112
 
110
113
  groups = [
111
- group.to_describe_dict()
112
- for name, group in copy_groups.items()
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 == "us-east-1":
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, PlatformApplication] = LocalAttribute(default=dict)
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)