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.
- localstack/aws/api/ec2/__init__.py +13 -0
- localstack/aws/api/iam/__init__.py +1 -0
- localstack/aws/api/lambda_/__init__.py +616 -0
- localstack/aws/api/logs/__init__.py +188 -0
- localstack/aws/api/opensearch/__init__.py +11 -0
- localstack/aws/api/route53/__init__.py +3 -0
- localstack/aws/api/s3/__init__.py +2 -0
- localstack/aws/api/s3control/__init__.py +19 -0
- localstack/aws/api/secretsmanager/__init__.py +9 -0
- localstack/aws/connect.py +35 -15
- localstack/aws/protocol/parser.py +6 -1
- localstack/aws/spec-patches.json +0 -38
- localstack/config.py +8 -0
- localstack/constants.py +3 -0
- localstack/dev/kubernetes/__main__.py +39 -14
- localstack/runtime/analytics.py +11 -0
- localstack/services/acm/provider.py +13 -1
- localstack/services/apigateway/legacy/provider.py +25 -4
- localstack/services/cloudformation/engine/v2/change_set_model.py +9 -0
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +3 -1
- localstack/services/cloudformation/engine/v2/change_set_resource_support_checker.py +114 -0
- localstack/services/cloudformation/provider.py +26 -1
- localstack/services/cloudformation/provider_utils.py +20 -0
- localstack/services/cloudformation/resource_provider.py +5 -4
- localstack/services/cloudformation/scaffolding/__main__.py +94 -22
- localstack/services/cloudformation/v2/provider.py +41 -0
- localstack/services/cloudwatch/models.py +10 -2
- localstack/services/cloudwatch/provider_v2.py +15 -20
- localstack/services/kinesis/packages.py +1 -1
- localstack/services/kms/models.py +6 -2
- localstack/services/lambda_/analytics.py +11 -2
- localstack/services/lambda_/invocation/event_manager.py +15 -11
- localstack/services/lambda_/invocation/lambda_models.py +4 -0
- localstack/services/lambda_/invocation/lambda_service.py +11 -0
- localstack/services/lambda_/provider.py +70 -13
- localstack/services/opensearch/packages.py +34 -20
- localstack/services/route53/provider.py +7 -0
- localstack/services/route53resolver/provider.py +5 -0
- localstack/services/s3/constants.py +5 -0
- localstack/services/s3/exceptions.py +9 -0
- localstack/services/s3/models.py +9 -1
- localstack/services/s3/provider.py +25 -30
- localstack/services/s3/utils.py +46 -1
- localstack/services/s3control/provider.py +6 -0
- localstack/services/scheduler/provider.py +4 -2
- localstack/services/secretsmanager/provider.py +4 -0
- localstack/services/ses/provider.py +4 -0
- localstack/services/sns/constants.py +13 -0
- localstack/services/sns/provider.py +5 -0
- localstack/services/sns/v2/models.py +4 -0
- localstack/services/sns/v2/provider.py +145 -0
- localstack/services/sqs/constants.py +6 -0
- localstack/services/sqs/provider.py +9 -1
- localstack/services/sqs/resource_providers/aws_sqs_queue.py +61 -46
- localstack/services/ssm/provider.py +6 -0
- localstack/services/stepfunctions/asl/static_analyser/test_state/test_state_analyser.py +193 -107
- localstack/services/stepfunctions/backend/execution.py +4 -5
- localstack/services/stepfunctions/provider.py +21 -14
- localstack/services/sts/provider.py +7 -0
- localstack/services/support/provider.py +5 -1
- localstack/services/swf/provider.py +5 -1
- localstack/services/transcribe/provider.py +7 -0
- localstack/testing/aws/lambda_utils.py +1 -1
- localstack/testing/aws/util.py +2 -1
- localstack/testing/config.py +1 -0
- localstack/utils/aws/client_types.py +2 -4
- localstack/utils/bootstrap.py +2 -2
- localstack/utils/catalog/catalog.py +3 -2
- localstack/utils/container_utils/container_client.py +22 -13
- localstack/utils/container_utils/docker_cmd_client.py +6 -6
- localstack/version.py +2 -2
- {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/METADATA +6 -6
- {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/RECORD +81 -80
- localstack_core-4.12.1.dev25.dist-info/plux.json +1 -0
- localstack_core-4.11.2.dev14.dist-info/plux.json +0 -1
- {localstack_core-4.11.2.dev14.data → localstack_core-4.12.1.dev25.data}/scripts/localstack +0 -0
- {localstack_core-4.11.2.dev14.data → localstack_core-4.12.1.dev25.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.11.2.dev14.data → localstack_core-4.12.1.dev25.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/WHEEL +0 -0
- {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/entry_points.txt +0 -0
- {localstack_core-4.11.2.dev14.dist-info → localstack_core-4.12.1.dev25.dist-info}/licenses/LICENSE.txt +0 -0
- {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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
new_config = dataclasses.replace(fn_version.config, state=new_state)
|
|
315
|
-
new_version = dataclasses.replace(fn_version, config=new_config)
|
|
316
|
-
fn.versions[fn_version.id.qualifier] = new_version
|
|
317
|
-
# TODO: consider skipping this for $LATEST versions of functions with a capacity provider
|
|
318
|
-
self.lambda_service.create_function_version(fn_version).result(
|
|
319
|
-
timeout=5
|
|
311
|
+
# $LATEST is not invokable for Lambda functions with a capacity provider
|
|
312
|
+
# and has a different State (i.e., ActiveNonInvokable)
|
|
313
|
+
is_capacity_provider_latest = (
|
|
314
|
+
fn_version.config.CapacityProviderConfig
|
|
315
|
+
and fn_version.id.qualifier == "$LATEST"
|
|
320
316
|
)
|
|
317
|
+
if not is_capacity_provider_latest:
|
|
318
|
+
# Restore the "Pending" state for the function version and start it
|
|
319
|
+
new_state = VersionState(
|
|
320
|
+
state=State.Pending,
|
|
321
|
+
code=StateReasonCode.Creating,
|
|
322
|
+
reason="The function is being created.",
|
|
323
|
+
)
|
|
324
|
+
new_config = dataclasses.replace(fn_version.config, state=new_state)
|
|
325
|
+
new_version = dataclasses.replace(fn_version, config=new_config)
|
|
326
|
+
fn.versions[fn_version.id.qualifier] = new_version
|
|
327
|
+
self.lambda_service.create_function_version(fn_version).result(
|
|
328
|
+
timeout=5
|
|
329
|
+
)
|
|
321
330
|
except Exception:
|
|
322
331
|
LOG.warning(
|
|
323
332
|
"Failed to restore function version %s",
|
|
@@ -853,6 +862,30 @@ class LambdaProvider(LambdaApi, ServiceLifecycleHook):
|
|
|
853
862
|
)
|
|
854
863
|
visited_layers[layer_arn] = layer_version_arn
|
|
855
864
|
|
|
865
|
+
def _validate_capacity_provider_config(
|
|
866
|
+
self, capacity_provider_config: CapacityProviderConfig, context: RequestContext
|
|
867
|
+
):
|
|
868
|
+
if not capacity_provider_config.get("LambdaManagedInstancesCapacityProviderConfig"):
|
|
869
|
+
raise ValidationException(
|
|
870
|
+
"1 validation error detected: Value null at 'capacityProviderConfig.lambdaManagedInstancesCapacityProviderConfig' failed to satisfy constraint: Member must not be null"
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
capacity_provider_arn = capacity_provider_config.get(
|
|
874
|
+
"LambdaManagedInstancesCapacityProviderConfig", {}
|
|
875
|
+
).get("CapacityProviderArn")
|
|
876
|
+
if not capacity_provider_arn:
|
|
877
|
+
raise ValidationException(
|
|
878
|
+
"1 validation error detected: Value null at 'capacityProviderConfig.lambdaManagedInstancesCapacityProviderConfig.capacityProviderArn' failed to satisfy constraint: Member must not be null"
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
if not re.match(CAPACITY_PROVIDER_ARN_NAME, capacity_provider_arn):
|
|
882
|
+
raise ValidationException(
|
|
883
|
+
f"1 validation error detected: Value '{capacity_provider_arn}' at 'capacityProviderConfig.lambdaManagedInstancesCapacityProviderConfig.capacityProviderArn' failed to satisfy constraint: Member must satisfy regular expression pattern: {CAPACITY_PROVIDER_ARN_NAME}"
|
|
884
|
+
)
|
|
885
|
+
|
|
886
|
+
capacity_provider_name = capacity_provider_arn.split(":")[-1]
|
|
887
|
+
self.get_capacity_provider(context, capacity_provider_name)
|
|
888
|
+
|
|
856
889
|
@staticmethod
|
|
857
890
|
def map_layers(new_layers: list[str]) -> list[LayerVersion]:
|
|
858
891
|
layers = []
|
|
@@ -1029,11 +1062,12 @@ class LambdaProvider(LambdaApi, ServiceLifecycleHook):
|
|
|
1029
1062
|
# Runtime management controls are not available when providing a custom image
|
|
1030
1063
|
runtime_version_config = None
|
|
1031
1064
|
|
|
1032
|
-
# TODO: validations and figure out in which order
|
|
1033
1065
|
capacity_provider_config = None
|
|
1034
1066
|
memory_size = request.get("MemorySize", LAMBDA_DEFAULT_MEMORY_SIZE)
|
|
1035
1067
|
if "CapacityProviderConfig" in request:
|
|
1036
1068
|
capacity_provider_config = request["CapacityProviderConfig"]
|
|
1069
|
+
self._validate_capacity_provider_config(capacity_provider_config, context)
|
|
1070
|
+
|
|
1037
1071
|
default_config = CapacityProviderConfig(
|
|
1038
1072
|
LambdaManagedInstancesCapacityProviderConfig=LambdaManagedInstancesCapacityProviderConfig(
|
|
1039
1073
|
ExecutionEnvironmentMemoryGiBPerVCpu=2.0,
|
|
@@ -1142,12 +1176,18 @@ class LambdaProvider(LambdaApi, ServiceLifecycleHook):
|
|
|
1142
1176
|
)
|
|
1143
1177
|
fn.versions["$LATEST"] = version_post_response or version
|
|
1144
1178
|
state.functions[function_name] = fn
|
|
1179
|
+
initialization_type = (
|
|
1180
|
+
FunctionInitializationType.lambda_managed_instances
|
|
1181
|
+
if capacity_provider_config
|
|
1182
|
+
else FunctionInitializationType.on_demand
|
|
1183
|
+
)
|
|
1145
1184
|
function_counter.labels(
|
|
1146
1185
|
operation=FunctionOperation.create,
|
|
1147
1186
|
runtime=runtime or "n/a",
|
|
1148
1187
|
status=FunctionStatus.success,
|
|
1149
1188
|
invocation_type="n/a",
|
|
1150
1189
|
package_type=package_type,
|
|
1190
|
+
initialization_type=initialization_type,
|
|
1151
1191
|
)
|
|
1152
1192
|
# TODO: consider potential other side effects of not having a function version for $LATEST
|
|
1153
1193
|
# Provisioning happens upon publishing for functions using a capacity provider
|
|
@@ -1363,6 +1403,19 @@ class LambdaProvider(LambdaApi, ServiceLifecycleHook):
|
|
|
1363
1403
|
if new_mode:
|
|
1364
1404
|
replace_kwargs["tracing_config_mode"] = new_mode
|
|
1365
1405
|
|
|
1406
|
+
if "CapacityProviderConfig" in request:
|
|
1407
|
+
if latest_version.config.CapacityProviderConfig and not request[
|
|
1408
|
+
"CapacityProviderConfig"
|
|
1409
|
+
].get("LambdaManagedInstancesCapacityProviderConfig"):
|
|
1410
|
+
raise ValidationException(
|
|
1411
|
+
"1 validation error detected: Value null at 'capacityProviderConfig.lambdaManagedInstancesCapacityProviderConfig' failed to satisfy constraint: Member must not be null"
|
|
1412
|
+
)
|
|
1413
|
+
if not latest_version.config.CapacityProviderConfig:
|
|
1414
|
+
raise InvalidParameterValueException(
|
|
1415
|
+
"CapacityProviderConfig isn't supported for Lambda Default functions.",
|
|
1416
|
+
Type="User",
|
|
1417
|
+
)
|
|
1418
|
+
|
|
1366
1419
|
new_latest_version = dataclasses.replace(
|
|
1367
1420
|
latest_version,
|
|
1368
1421
|
config=dataclasses.replace(
|
|
@@ -1701,6 +1754,7 @@ class LambdaProvider(LambdaApi, ServiceLifecycleHook):
|
|
|
1701
1754
|
invocation_type: InvocationType | None = None,
|
|
1702
1755
|
log_type: LogType | None = None,
|
|
1703
1756
|
client_context: String | None = None,
|
|
1757
|
+
durable_execution_name: DurableExecutionName | None = None,
|
|
1704
1758
|
payload: IO[Blob] | None = None,
|
|
1705
1759
|
qualifier: NumericLatestPublishedOrAliasQualifier | None = None,
|
|
1706
1760
|
tenant_id: TenantId | None = None,
|
|
@@ -2210,6 +2264,9 @@ class LambdaProvider(LambdaApi, ServiceLifecycleHook):
|
|
|
2210
2264
|
raise Exception("unknown version") # TODO: cover via test
|
|
2211
2265
|
elif qualifier == "$LATEST":
|
|
2212
2266
|
pass
|
|
2267
|
+
elif qualifier == "$LATEST.PUBLISHED":
|
|
2268
|
+
if fn.versions.get(qualifier):
|
|
2269
|
+
pass
|
|
2213
2270
|
else:
|
|
2214
2271
|
raise Exception("invalid functionname") # TODO: cover via test
|
|
2215
2272
|
fn_arn = api_utils.qualified_lambda_arn(function_name, qualifier, account, region)
|
|
@@ -107,19 +107,31 @@ class OpensearchPackageInstaller(PackageInstaller):
|
|
|
107
107
|
# setup security based on the version
|
|
108
108
|
self._setup_security(install_dir, parsed_version)
|
|
109
109
|
|
|
110
|
+
# Determine network configuration to use for plugin downloads
|
|
111
|
+
sys_props = {
|
|
112
|
+
**java_system_properties_proxy(),
|
|
113
|
+
**java_system_properties_ssl(
|
|
114
|
+
os.path.join(install_dir, "jdk", "bin", "keytool"),
|
|
115
|
+
{"JAVA_HOME": os.path.join(install_dir, "jdk")},
|
|
116
|
+
),
|
|
117
|
+
}
|
|
118
|
+
java_opts = system_properties_to_cli_args(sys_props)
|
|
119
|
+
|
|
120
|
+
keystore_binary = os.path.join(install_dir, "bin", "opensearch-keystore")
|
|
121
|
+
if os.path.exists(keystore_binary):
|
|
122
|
+
# initialize and create the keystore. Concurrent starts of ES will all try to create it at the same
|
|
123
|
+
# time, and fail with a race condition. Creating once when installing solves the issue without
|
|
124
|
+
# the need to lock the starts
|
|
125
|
+
# Ultimately, each cluster should have its own `config` file and maybe not share the same one
|
|
126
|
+
output = run(
|
|
127
|
+
[keystore_binary, "create"],
|
|
128
|
+
env_vars={"OPENSEARCH_JAVA_OPTS": " ".join(java_opts)},
|
|
129
|
+
)
|
|
130
|
+
LOG.debug("Keystore init output: %s", output)
|
|
131
|
+
|
|
110
132
|
# install other default plugins for opensearch 1.1+
|
|
111
133
|
# https://forum.opensearch.org/t/ingest-attachment-cannot-be-installed/6494/12
|
|
112
134
|
if parsed_version >= "1.1.0":
|
|
113
|
-
# Determine network configuration to use for plugin downloads
|
|
114
|
-
sys_props = {
|
|
115
|
-
**java_system_properties_proxy(),
|
|
116
|
-
**java_system_properties_ssl(
|
|
117
|
-
os.path.join(install_dir, "jdk", "bin", "keytool"),
|
|
118
|
-
{"JAVA_HOME": os.path.join(install_dir, "jdk")},
|
|
119
|
-
),
|
|
120
|
-
}
|
|
121
|
-
java_opts = system_properties_to_cli_args(sys_props)
|
|
122
|
-
|
|
123
135
|
for plugin in OPENSEARCH_PLUGIN_LIST:
|
|
124
136
|
plugin_binary = os.path.join(install_dir, "bin", "opensearch-plugin")
|
|
125
137
|
plugin_dir = os.path.join(install_dir, "plugins", plugin)
|
|
@@ -322,6 +334,18 @@ class ElasticsearchPackageInstaller(PackageInstaller):
|
|
|
322
334
|
if not os.environ.get("IGNORE_ES_DOWNLOAD_ERRORS"):
|
|
323
335
|
raise
|
|
324
336
|
|
|
337
|
+
keystore_binary = os.path.join(install_dir, "bin", "elasticsearch-keystore")
|
|
338
|
+
if os.path.exists(keystore_binary):
|
|
339
|
+
# initialize and create the keystore. Concurrent starts of ES will all try to create it at the same
|
|
340
|
+
# time, and fail with a race condition. Creating once when installing solves the issue without
|
|
341
|
+
# the need to lock the starts
|
|
342
|
+
# Ultimately, each cluster should have its own `config` file and maybe not share the same one
|
|
343
|
+
output = run(
|
|
344
|
+
[keystore_binary, "create"],
|
|
345
|
+
env_vars={"ES_JAVA_OPTS": " ".join(java_opts)},
|
|
346
|
+
)
|
|
347
|
+
LOG.debug("Keystore init output: %s", output)
|
|
348
|
+
|
|
325
349
|
# delete some plugins to free up space
|
|
326
350
|
for plugin in ELASTICSEARCH_DELETE_MODULES:
|
|
327
351
|
module_dir = os.path.join(install_dir, "modules", plugin)
|
|
@@ -341,16 +365,6 @@ class ElasticsearchPackageInstaller(PackageInstaller):
|
|
|
341
365
|
if jvm_options != jvm_options_replaced:
|
|
342
366
|
save_file(jvm_options_file, jvm_options_replaced)
|
|
343
367
|
|
|
344
|
-
# patch JVM options file - replace hardcoded heap size settings
|
|
345
|
-
jvm_options_file = os.path.join(install_dir, "config", "jvm.options")
|
|
346
|
-
if os.path.exists(jvm_options_file):
|
|
347
|
-
jvm_options = load_file(jvm_options_file)
|
|
348
|
-
jvm_options_replaced = re.sub(
|
|
349
|
-
r"(^-Xm[sx][a-zA-Z0-9.]+$)", r"# \1", jvm_options, flags=re.MULTILINE
|
|
350
|
-
)
|
|
351
|
-
if jvm_options != jvm_options_replaced:
|
|
352
|
-
save_file(jvm_options_file, jvm_options_replaced)
|
|
353
|
-
|
|
354
368
|
def _get_install_marker_path(self, install_dir: str) -> str:
|
|
355
369
|
return os.path.join(install_dir, "bin", "elasticsearch")
|
|
356
370
|
|
|
@@ -26,9 +26,16 @@ from localstack.aws.api.route53 import (
|
|
|
26
26
|
from localstack.aws.connect import connect_to
|
|
27
27
|
from localstack.services.moto import call_moto
|
|
28
28
|
from localstack.services.plugins import ServiceLifecycleHook
|
|
29
|
+
from localstack.state import StateVisitor
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
class Route53Provider(Route53Api, ServiceLifecycleHook):
|
|
33
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
34
|
+
from localstack.services.route53.models import route53_stores
|
|
35
|
+
|
|
36
|
+
visitor.visit(route53_backends)
|
|
37
|
+
visitor.visit(route53_stores)
|
|
38
|
+
|
|
32
39
|
def create_hosted_zone(
|
|
33
40
|
self,
|
|
34
41
|
context: RequestContext,
|
|
@@ -100,6 +100,7 @@ from localstack.services.route53resolver.utils import (
|
|
|
100
100
|
validate_mutation_protection,
|
|
101
101
|
validate_priority,
|
|
102
102
|
)
|
|
103
|
+
from localstack.state import StateVisitor
|
|
103
104
|
from localstack.utils.aws import arns
|
|
104
105
|
from localstack.utils.aws.arns import extract_account_id_from_arn, extract_region_from_arn
|
|
105
106
|
from localstack.utils.collections import select_from_typed_dict
|
|
@@ -107,6 +108,10 @@ from localstack.utils.patch import patch
|
|
|
107
108
|
|
|
108
109
|
|
|
109
110
|
class Route53ResolverProvider(Route53ResolverApi):
|
|
111
|
+
def accept_state_visitor(self, visitor: StateVisitor):
|
|
112
|
+
visitor.visit(route53resolver_backends)
|
|
113
|
+
visitor.visit(route53resolver_stores)
|
|
114
|
+
|
|
110
115
|
@staticmethod
|
|
111
116
|
def get_store(account_id: str, region: str) -> Route53ResolverStore:
|
|
112
117
|
return route53resolver_stores[account_id][region]
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from localstack.aws.api.s3 import (
|
|
2
|
+
BucketLocationConstraint,
|
|
2
3
|
ChecksumAlgorithm,
|
|
3
4
|
Grantee,
|
|
4
5
|
Permission,
|
|
@@ -66,6 +67,10 @@ CHECKSUM_ALGORITHMS: list[ChecksumAlgorithm] = [
|
|
|
66
67
|
ChecksumAlgorithm.CRC64NVME,
|
|
67
68
|
]
|
|
68
69
|
|
|
70
|
+
BUCKET_LOCATION_CONSTRAINTS = [constraint.value for constraint in BucketLocationConstraint]
|
|
71
|
+
|
|
72
|
+
EU_WEST_1_LOCATION_CONSTRAINTS = [BucketLocationConstraint.EU, BucketLocationConstraint.eu_west_1]
|
|
73
|
+
|
|
69
74
|
# response header overrides the client may request
|
|
70
75
|
ALLOWED_HEADER_OVERRIDES = {
|
|
71
76
|
"ResponseContentType": "ContentType",
|
|
@@ -51,3 +51,12 @@ class InvalidBucketOwnerAWSAccountID(CommonServiceException):
|
|
|
51
51
|
class TooManyConfigurations(CommonServiceException):
|
|
52
52
|
def __init__(self, message=None) -> None:
|
|
53
53
|
super().__init__("TooManyConfigurations", status_code=400, message=message)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class IllegalLocationConstraintException(CommonServiceException):
|
|
57
|
+
def __init__(self, location_constraint: str):
|
|
58
|
+
super().__init__(
|
|
59
|
+
"IllegalLocationConstraintException",
|
|
60
|
+
status_code=400,
|
|
61
|
+
message=f"The {location_constraint} location constraint is incompatible for the region specific endpoint this request was sent to.",
|
|
62
|
+
)
|
localstack/services/s3/models.py
CHANGED
|
@@ -16,6 +16,7 @@ from localstack.aws.api.s3 import (
|
|
|
16
16
|
BadDigest,
|
|
17
17
|
BucketAccelerateStatus,
|
|
18
18
|
BucketKeyEnabled,
|
|
19
|
+
BucketLocationConstraint,
|
|
19
20
|
BucketName,
|
|
20
21
|
BucketRegion,
|
|
21
22
|
BucketVersioningStatus,
|
|
@@ -76,7 +77,11 @@ from localstack.services.s3.constants import (
|
|
|
76
77
|
S3_UPLOAD_PART_MIN_SIZE,
|
|
77
78
|
)
|
|
78
79
|
from localstack.services.s3.exceptions import InvalidRequest
|
|
79
|
-
from localstack.services.s3.utils import
|
|
80
|
+
from localstack.services.s3.utils import (
|
|
81
|
+
CombinedCrcHash,
|
|
82
|
+
get_s3_checksum,
|
|
83
|
+
rfc_1123_datetime,
|
|
84
|
+
)
|
|
80
85
|
from localstack.services.stores import (
|
|
81
86
|
AccountRegionBundle,
|
|
82
87
|
BaseStore,
|
|
@@ -101,6 +106,7 @@ class S3Bucket:
|
|
|
101
106
|
name: BucketName
|
|
102
107
|
bucket_account_id: AccountId
|
|
103
108
|
bucket_region: BucketRegion
|
|
109
|
+
location_constraint: BucketLocationConstraint | Literal[""]
|
|
104
110
|
creation_date: datetime
|
|
105
111
|
multiparts: dict[MultipartUploadId, "S3Multipart"]
|
|
106
112
|
objects: Union["KeyStore", "VersionedKeyStore"]
|
|
@@ -137,10 +143,12 @@ class S3Bucket:
|
|
|
137
143
|
acl: AccessControlPolicy = None,
|
|
138
144
|
object_ownership: ObjectOwnership = None,
|
|
139
145
|
object_lock_enabled_for_bucket: bool = None,
|
|
146
|
+
location_constraint: BucketLocationConstraint | Literal[""] = "",
|
|
140
147
|
):
|
|
141
148
|
self.name = name
|
|
142
149
|
self.bucket_account_id = account_id
|
|
143
150
|
self.bucket_region = bucket_region
|
|
151
|
+
self.location_constraint = location_constraint
|
|
144
152
|
# If ObjectLock is enabled, it forces the bucket to be versioned as well
|
|
145
153
|
self.versioning_status = None if not object_lock_enabled_for_bucket else "Enabled"
|
|
146
154
|
self.objects = KeyStore() if not object_lock_enabled_for_bucket else VersionedKeyStore()
|
|
@@ -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
|
-
|
|
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
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
-
|
|
515
|
-
|
|
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
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
)
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
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
|
-
|
|
721
|
-
|
|
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(
|
localstack/services/s3/utils.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 + "." + "-" + "_")
|