localstack-core 4.11.2.dev14__py3-none-any.whl → 4.12.1.dev25__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 (82) hide show
  1. localstack/aws/api/ec2/__init__.py +13 -0
  2. localstack/aws/api/iam/__init__.py +1 -0
  3. localstack/aws/api/lambda_/__init__.py +616 -0
  4. localstack/aws/api/logs/__init__.py +188 -0
  5. localstack/aws/api/opensearch/__init__.py +11 -0
  6. localstack/aws/api/route53/__init__.py +3 -0
  7. localstack/aws/api/s3/__init__.py +2 -0
  8. localstack/aws/api/s3control/__init__.py +19 -0
  9. localstack/aws/api/secretsmanager/__init__.py +9 -0
  10. localstack/aws/connect.py +35 -15
  11. localstack/aws/protocol/parser.py +6 -1
  12. localstack/aws/spec-patches.json +0 -38
  13. localstack/config.py +8 -0
  14. localstack/constants.py +3 -0
  15. localstack/dev/kubernetes/__main__.py +39 -14
  16. localstack/runtime/analytics.py +11 -0
  17. localstack/services/acm/provider.py +13 -1
  18. localstack/services/apigateway/legacy/provider.py +25 -4
  19. localstack/services/cloudformation/engine/v2/change_set_model.py +9 -0
  20. localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +3 -1
  21. localstack/services/cloudformation/engine/v2/change_set_resource_support_checker.py +114 -0
  22. localstack/services/cloudformation/provider.py +26 -1
  23. localstack/services/cloudformation/provider_utils.py +20 -0
  24. localstack/services/cloudformation/resource_provider.py +5 -4
  25. localstack/services/cloudformation/scaffolding/__main__.py +94 -22
  26. localstack/services/cloudformation/v2/provider.py +41 -0
  27. localstack/services/cloudwatch/models.py +10 -2
  28. localstack/services/cloudwatch/provider_v2.py +15 -20
  29. localstack/services/kinesis/packages.py +1 -1
  30. localstack/services/kms/models.py +6 -2
  31. localstack/services/lambda_/analytics.py +11 -2
  32. localstack/services/lambda_/invocation/event_manager.py +15 -11
  33. localstack/services/lambda_/invocation/lambda_models.py +4 -0
  34. localstack/services/lambda_/invocation/lambda_service.py +11 -0
  35. localstack/services/lambda_/provider.py +70 -13
  36. localstack/services/opensearch/packages.py +34 -20
  37. localstack/services/route53/provider.py +7 -0
  38. localstack/services/route53resolver/provider.py +5 -0
  39. localstack/services/s3/constants.py +5 -0
  40. localstack/services/s3/exceptions.py +9 -0
  41. localstack/services/s3/models.py +9 -1
  42. localstack/services/s3/provider.py +25 -30
  43. localstack/services/s3/utils.py +46 -1
  44. localstack/services/s3control/provider.py +6 -0
  45. localstack/services/scheduler/provider.py +4 -2
  46. localstack/services/secretsmanager/provider.py +4 -0
  47. localstack/services/ses/provider.py +4 -0
  48. localstack/services/sns/constants.py +13 -0
  49. localstack/services/sns/provider.py +5 -0
  50. localstack/services/sns/v2/models.py +4 -0
  51. localstack/services/sns/v2/provider.py +145 -0
  52. localstack/services/sqs/constants.py +6 -0
  53. localstack/services/sqs/provider.py +9 -1
  54. localstack/services/sqs/resource_providers/aws_sqs_queue.py +61 -46
  55. localstack/services/ssm/provider.py +6 -0
  56. localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py +193 -107
  57. localstack/services/stepfunctions/backend/execution.py +4 -5
  58. localstack/services/stepfunctions/provider.py +21 -14
  59. localstack/services/sts/provider.py +7 -0
  60. localstack/services/support/provider.py +5 -1
  61. localstack/services/swf/provider.py +5 -1
  62. localstack/services/transcribe/provider.py +7 -0
  63. localstack/testing/aws/lambda_utils.py +1 -1
  64. localstack/testing/aws/util.py +2 -1
  65. localstack/testing/config.py +1 -0
  66. localstack/utils/aws/client_types.py +2 -4
  67. localstack/utils/bootstrap.py +2 -2
  68. localstack/utils/catalog/catalog.py +3 -2
  69. localstack/utils/container_utils/container_client.py +22 -13
  70. localstack/utils/container_utils/docker_cmd_client.py +6 -6
  71. localstack/version.py +2 -2
  72. {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/METADATA +6 -6
  73. {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/RECORD +81 -80
  74. localstack_core-4.12.1.dev25.dist-info/plux.json +1 -0
  75. localstack_core-4.11.2.dev14.dist-info/plux.json +0 -1
  76. {localstack_core-4.11.2.dev14.data → localstack_core-4.12.1.dev25.data}/scripts/localstack +0 -0
  77. {localstack_core-4.11.2.dev14.data → localstack_core-4.12.1.dev25.data}/scripts/localstack-supervisor +0 -0
  78. {localstack_core-4.11.2.dev14.data → localstack_core-4.12.1.dev25.data}/scripts/localstack.bat +0 -0
  79. {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/WHEEL +0 -0
  80. {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/entry_points.txt +0 -0
  81. {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/licenses/LICENSE.txt +0 -0
  82. {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/top_level.txt +0 -0
@@ -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
- new_state = VersionState(
310
- state=State.Pending,
311
- code=StateReasonCode.Creating,
312
- reason="The function is being created.",
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
+ )
@@ -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 CombinedCrcHash, get_s3_checksum, rfc_1123_datetime
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()
@@ -33,6 +33,7 @@ from localstack.aws.api.s3 import (
33
33
  BucketAlreadyOwnedByYou,
34
34
  BucketCannedACL,
35
35
  BucketLifecycleConfiguration,
36
+ BucketLocationConstraint,
36
37
  BucketLoggingStatus,
37
38
  BucketName,
38
39
  BucketNotEmpty,
@@ -117,7 +118,6 @@ from localstack.aws.api.s3 import (
117
118
  InvalidArgument,
118
119
  InvalidBucketName,
119
120
  InvalidDigest,
120
- InvalidLocationConstraint,
121
121
  InvalidObjectState,
122
122
  InvalidPartNumber,
123
123
  InvalidPartOrder,
@@ -229,7 +229,7 @@ from localstack.aws.handlers import (
229
229
  preprocess_request,
230
230
  serve_custom_service_request_handlers,
231
231
  )
232
- from localstack.constants import AWS_REGION_US_EAST_1
232
+ from localstack.constants import AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1
233
233
  from localstack.services.edge import ROUTER
234
234
  from localstack.services.plugins import ServiceLifecycleHook
235
235
  from localstack.services.s3.codec import AwsChunkedDecoder
@@ -278,6 +278,7 @@ from localstack.services.s3.utils import (
278
278
  etag_to_base_64_content_md5,
279
279
  extract_bucket_key_version_id_from_copy_source,
280
280
  generate_safe_version_id,
281
+ get_bucket_location_xml,
281
282
  get_canned_acl,
282
283
  get_class_attrs_from_spec_class,
283
284
  get_failed_precondition_copy_source,
@@ -304,6 +305,7 @@ from localstack.services.s3.utils import (
304
305
  validate_dict_fields,
305
306
  validate_failed_precondition,
306
307
  validate_kms_key_id,
308
+ validate_location_constraint,
307
309
  validate_tag_set,
308
310
  )
309
311
  from localstack.services.s3.validation import (
@@ -493,29 +495,19 @@ class S3Provider(S3Api, ServiceLifecycleHook):
493
495
  raise InvalidBucketName("The specified bucket is not valid.", BucketName=bucket_name)
494
496
 
495
497
  create_bucket_configuration = request.get("CreateBucketConfiguration") or {}
496
- bucket_region = create_bucket_configuration.get("LocationConstraint")
498
+
497
499
  bucket_tags = create_bucket_configuration.get("Tags")
498
500
  if bucket_tags:
499
501
  validate_tag_set(bucket_tags, type_set="create-bucket")
500
- if bucket_region:
501
- if context.region == AWS_REGION_US_EAST_1:
502
- if bucket_region in ("us-east-1", "aws-global"):
503
- raise InvalidLocationConstraint(
504
- "The specified location-constraint is not valid",
505
- LocationConstraint=bucket_region,
506
- )
507
- elif context.region != bucket_region:
508
- raise CommonServiceException(
509
- code="IllegalLocationConstraintException",
510
- message=f"The {bucket_region} location constraint is incompatible for the region specific endpoint this request was sent to.",
511
- )
512
- else:
502
+
503
+ location_constraint = create_bucket_configuration.get("LocationConstraint", "")
504
+ validate_location_constraint(context.region, location_constraint)
505
+
506
+ bucket_region = location_constraint
507
+ if not location_constraint:
513
508
  bucket_region = AWS_REGION_US_EAST_1
514
- if context.region != bucket_region:
515
- raise CommonServiceException(
516
- code="IllegalLocationConstraintException",
517
- message="The unspecified location constraint is incompatible for the region specific endpoint this request was sent to.",
518
- )
509
+ if location_constraint == BucketLocationConstraint.EU:
510
+ bucket_region = AWS_REGION_EU_WEST_1
519
511
 
520
512
  store = self.get_store(context.account_id, bucket_region)
521
513
 
@@ -554,6 +546,7 @@ class S3Provider(S3Api, ServiceLifecycleHook):
554
546
  acl=acl,
555
547
  object_ownership=request.get("ObjectOwnership"),
556
548
  object_lock_enabled_for_bucket=request.get("ObjectLockEnabledForBucket"),
549
+ location_constraint=location_constraint,
557
550
  )
558
551
 
559
552
  store.buckets[bucket_name] = s3_bucket
@@ -709,16 +702,18 @@ class S3Provider(S3Api, ServiceLifecycleHook):
709
702
  """
710
703
  store, s3_bucket = self._get_cross_account_bucket(context, bucket)
711
704
 
712
- location_constraint = (
713
- '<?xml version="1.0" encoding="UTF-8"?>\n'
714
- '<LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/">{{location}}</LocationConstraint>'
715
- )
716
-
717
- location = s3_bucket.bucket_region if s3_bucket.bucket_region != "us-east-1" else ""
718
- location_constraint = location_constraint.replace("{{location}}", location)
705
+ # TODO: Remove usage of getattr once persistence mechanism is updated.
706
+ # If the stored constraint is None the bucket was made before the storage of location_constraint.
707
+ # The EU location constraint wasn't supported before this point so we can safely default to the region.
708
+ location_constraint = getattr(s3_bucket, "location_constraint", None)
709
+ if location_constraint is None:
710
+ location_constraint = (
711
+ s3_bucket.bucket_region if s3_bucket.bucket_region != "us-east-1" else ""
712
+ )
719
713
 
720
- response = GetBucketLocationOutput(LocationConstraint=location_constraint)
721
- return response
714
+ return GetBucketLocationOutput(
715
+ LocationConstraint=get_bucket_location_xml(location_constraint)
716
+ )
722
717
 
723
718
  @handler("PutObject", expand=False)
724
719
  def put_object(
@@ -34,6 +34,7 @@ from localstack.aws.api.s3 import (
34
34
  Grantee,
35
35
  HeadObjectRequest,
36
36
  InvalidArgument,
37
+ InvalidLocationConstraint,
37
38
  InvalidRange,
38
39
  InvalidTag,
39
40
  LifecycleExpiration,
@@ -57,18 +58,25 @@ from localstack.aws.api.s3 import (
57
58
  from localstack.aws.api.s3 import Type as GranteeType
58
59
  from localstack.aws.chain import HandlerChain
59
60
  from localstack.aws.connect import connect_to
61
+ from localstack.constants import AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1
60
62
  from localstack.http import Response
61
63
  from localstack.services.s3 import checksums
62
64
  from localstack.services.s3.constants import (
63
65
  ALL_USERS_ACL_GRANTEE,
64
66
  AUTHENTICATED_USERS_ACL_GRANTEE,
67
+ BUCKET_LOCATION_CONSTRAINTS,
65
68
  CHECKSUM_ALGORITHMS,
69
+ EU_WEST_1_LOCATION_CONSTRAINTS,
66
70
  LOG_DELIVERY_ACL_GRANTEE,
67
71
  SIGNATURE_V2_PARAMS,
68
72
  SIGNATURE_V4_PARAMS,
69
73
  SYSTEM_METADATA_SETTABLE_HEADERS,
70
74
  )
71
- from localstack.services.s3.exceptions import InvalidRequest, MalformedXML
75
+ from localstack.services.s3.exceptions import (
76
+ IllegalLocationConstraintException,
77
+ InvalidRequest,
78
+ MalformedXML,
79
+ )
72
80
  from localstack.utils.aws import arns
73
81
  from localstack.utils.aws.arns import parse_arn
74
82
  from localstack.utils.objects import singleton_factory
@@ -888,6 +896,27 @@ def validate_tag_set(
888
896
  keys.add(key)
889
897
 
890
898
 
899
+ def validate_location_constraint(context_region: str, location_constraint: str) -> None:
900
+ if location_constraint:
901
+ if context_region == AWS_REGION_US_EAST_1:
902
+ if (
903
+ not config.ALLOW_NONSTANDARD_REGIONS
904
+ and location_constraint not in BUCKET_LOCATION_CONSTRAINTS
905
+ ):
906
+ raise InvalidLocationConstraint(
907
+ "The specified location-constraint is not valid",
908
+ LocationConstraint=location_constraint,
909
+ )
910
+ elif context_region == AWS_REGION_EU_WEST_1:
911
+ if location_constraint not in EU_WEST_1_LOCATION_CONSTRAINTS:
912
+ raise IllegalLocationConstraintException(location_constraint)
913
+ elif context_region != location_constraint:
914
+ raise IllegalLocationConstraintException(location_constraint)
915
+ else:
916
+ if context_region != AWS_REGION_US_EAST_1:
917
+ raise IllegalLocationConstraintException("unspecified")
918
+
919
+
891
920
  def get_unique_key_id(
892
921
  bucket: BucketName, object_key: ObjectKey, version_id: ObjectVersionId
893
922
  ) -> str:
@@ -1105,3 +1134,19 @@ def is_version_older_than_other(version_id: str, other: str):
1105
1134
  See `generate_safe_version_id`
1106
1135
  """
1107
1136
  return base64.b64decode(version_id, altchars=b"._") < base64.b64decode(other, altchars=b"._")
1137
+
1138
+
1139
+ def get_bucket_location_xml(location_constraint: str) -> str:
1140
+ """
1141
+ Returns the formatted XML for the GetBucketLocation operation.
1142
+
1143
+ :param location_constraint: The location constraint to return in the XML. It can be an empty string when
1144
+ it's not specified in the bucket configuration.
1145
+ :return: The XML response.
1146
+ """
1147
+
1148
+ return (
1149
+ '<?xml version="1.0" encoding="UTF-8"?>\n'
1150
+ '<LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/"'
1151
+ + ("/>" if not location_constraint else f">{location_constraint}</LocationConstraint>")
1152
+ )
@@ -12,6 +12,7 @@ from localstack.aws.api.s3control import (
12
12
  from localstack.aws.forwarder import NotImplementedAvoidFallbackError
13
13
  from localstack.services.s3.models import s3_stores
14
14
  from localstack.services.s3control.validation import validate_tags
15
+ from localstack.state import StateVisitor
15
16
  from localstack.utils.tagging import TaggingService
16
17
 
17
18
 
@@ -21,6 +22,11 @@ class NoSuchResource(CommonServiceException):
21
22
 
22
23
 
23
24
  class S3ControlProvider(S3ControlApi):
25
+ def accept_state_visitor(self, visitor: StateVisitor):
26
+ from moto.s3control.models import s3control_backends
27
+
28
+ visitor.visit(s3control_backends)
29
+
24
30
  """
25
31
  S3Control is a management interface for S3, and can access some of its internals with no public API
26
32
  This requires us to access the s3 stores directly
@@ -1,11 +1,12 @@
1
1
  import logging
2
2
  import re
3
3
 
4
- from moto.scheduler.models import EventBridgeSchedulerBackend
4
+ from moto.scheduler.models import EventBridgeSchedulerBackend, scheduler_backends
5
5
 
6
6
  from localstack.aws.api.scheduler import SchedulerApi, ValidationException
7
7
  from localstack.services.events.rule import RULE_SCHEDULE_CRON_REGEX, RULE_SCHEDULE_RATE_REGEX
8
8
  from localstack.services.plugins import ServiceLifecycleHook
9
+ from localstack.state import StateVisitor
9
10
  from localstack.utils.patch import patch
10
11
 
11
12
  LOG = logging.getLogger(__name__)
@@ -17,7 +18,8 @@ RULE_SCHEDULE_AT_REGEX = re.compile(AT_REGEX)
17
18
 
18
19
 
19
20
  class SchedulerProvider(SchedulerApi, ServiceLifecycleHook):
20
- pass
21
+ def accept_state_visitor(self, visitor: StateVisitor):
22
+ visitor.visit(scheduler_backends)
21
23
 
22
24
 
23
25
  def _validate_schedule_expression(schedule_expression: str) -> None:
@@ -65,6 +65,7 @@ from localstack.aws.api.secretsmanager import (
65
65
  )
66
66
  from localstack.aws.connect import connect_to
67
67
  from localstack.services.moto import call_moto
68
+ from localstack.state import StateVisitor
68
69
  from localstack.utils.aws import arns
69
70
  from localstack.utils.patch import patch
70
71
  from localstack.utils.time import today_no_time
@@ -105,6 +106,9 @@ class SecretsmanagerProvider(SecretsmanagerApi):
105
106
  super().__init__()
106
107
  apply_patches()
107
108
 
109
+ def accept_state_visitor(self, visitor: StateVisitor):
110
+ visitor.visit(secretsmanager_backends)
111
+
108
112
  @staticmethod
109
113
  def get_moto_backend_for_resource(
110
114
  name_or_arn: str, context: RequestContext
@@ -62,6 +62,7 @@ from localstack.http import Resource, Response
62
62
  from localstack.services.moto import call_moto
63
63
  from localstack.services.plugins import ServiceLifecycleHook
64
64
  from localstack.services.ses.models import EmailType, SentEmail, SentEmailBody
65
+ from localstack.state import StateVisitor
65
66
  from localstack.utils.aws import arns
66
67
  from localstack.utils.files import mkdir
67
68
  from localstack.utils.strings import long_uid, to_str
@@ -177,6 +178,9 @@ def register_ses_api_resource():
177
178
 
178
179
 
179
180
  class SesProvider(SesApi, ServiceLifecycleHook):
181
+ def accept_state_visitor(self, visitor: StateVisitor):
182
+ visitor.visit(ses_backends)
183
+
180
184
  #
181
185
  # Lifecycle Hooks
182
186
  #
@@ -25,6 +25,19 @@ VALID_SUBSCRIPTION_ATTR_NAME: list[str] = [
25
25
  "SubscriptionRoleArn",
26
26
  ]
27
27
 
28
+
29
+ VALID_POLICY_ACTIONS = [
30
+ "GetTopicAttributes",
31
+ "SetTopicAttributes",
32
+ "AddPermission",
33
+ "RemovePermission",
34
+ "DeleteTopic",
35
+ "Subscribe",
36
+ "ListSubscriptionsByTopic",
37
+ "Publish",
38
+ "Receive",
39
+ ]
40
+
28
41
  MSG_ATTR_NAME_REGEX = re.compile(r"^(?!\.)(?!.*\.$)(?!.*\.\.)[a-zA-Z0-9_\-.]+$")
29
42
  ATTR_TYPE_REGEX = re.compile(r"^(String|Number|Binary)\..+$")
30
43
  VALID_MSG_ATTR_NAME_CHARS = set(ascii_letters + digits + "." + "-" + "_")