localstack-core 4.7.1.dev139__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.
Potentially problematic release.
This version of localstack-core might be problematic. Click here for more details.
- localstack/aws/api/acm/__init__.py +122 -122
- localstack/aws/api/apigateway/__init__.py +560 -559
- localstack/aws/api/cloudcontrol/__init__.py +63 -63
- localstack/aws/api/cloudformation/__init__.py +1041 -969
- localstack/aws/api/cloudwatch/__init__.py +408 -368
- localstack/aws/api/config/__init__.py +788 -786
- localstack/aws/api/core.py +4 -0
- localstack/aws/api/dynamodb/__init__.py +753 -759
- localstack/aws/api/dynamodbstreams/__init__.py +74 -74
- localstack/aws/api/ec2/__init__.py +9713 -8573
- localstack/aws/api/es/__init__.py +453 -453
- localstack/aws/api/events/__init__.py +552 -552
- localstack/aws/api/firehose/__init__.py +541 -543
- localstack/aws/api/iam/__init__.py +646 -572
- localstack/aws/api/kinesis/__init__.py +251 -144
- localstack/aws/api/kms/__init__.py +343 -333
- localstack/aws/api/lambda_/__init__.py +585 -571
- localstack/aws/api/logs/__init__.py +682 -666
- localstack/aws/api/opensearch/__init__.py +814 -785
- localstack/aws/api/pipes/__init__.py +336 -336
- localstack/aws/api/redshift/__init__.py +1192 -1164
- localstack/aws/api/resource_groups/__init__.py +175 -175
- localstack/aws/api/resourcegroupstaggingapi/__init__.py +67 -67
- localstack/aws/api/route53/__init__.py +256 -254
- localstack/aws/api/route53resolver/__init__.py +396 -396
- localstack/aws/api/s3/__init__.py +1358 -1345
- localstack/aws/api/s3control/__init__.py +616 -584
- localstack/aws/api/scheduler/__init__.py +118 -118
- localstack/aws/api/secretsmanager/__init__.py +193 -193
- localstack/aws/api/ses/__init__.py +227 -227
- localstack/aws/api/sns/__init__.py +115 -115
- localstack/aws/api/sqs/__init__.py +100 -100
- localstack/aws/api/ssm/__init__.py +1978 -1970
- localstack/aws/api/stepfunctions/__init__.py +323 -323
- localstack/aws/api/sts/__init__.py +90 -66
- localstack/aws/api/support/__init__.py +112 -112
- localstack/aws/api/swf/__init__.py +378 -386
- localstack/aws/api/transcribe/__init__.py +425 -425
- localstack/aws/client.py +7 -2
- localstack/aws/forwarder.py +52 -5
- localstack/aws/handlers/analytics.py +1 -1
- localstack/aws/handlers/logging.py +12 -2
- localstack/aws/handlers/metric_handler.py +41 -1
- localstack/aws/handlers/service.py +43 -10
- localstack/aws/protocol/parser.py +440 -21
- localstack/aws/protocol/serializer.py +684 -64
- localstack/aws/protocol/service_router.py +120 -20
- localstack/aws/scaffold.py +15 -17
- localstack/aws/skeleton.py +4 -2
- localstack/aws/spec-patches.json +58 -0
- localstack/aws/spec.py +33 -13
- localstack/cli/exceptions.py +1 -1
- localstack/cli/localstack.py +10 -5
- localstack/cli/lpm.py +3 -4
- localstack/cli/profiles.py +1 -2
- localstack/config.py +18 -12
- localstack/constants.py +4 -29
- localstack/dev/kubernetes/__main__.py +39 -4
- localstack/dev/run/paths.py +1 -1
- localstack/dns/plugins.py +5 -1
- localstack/dns/server.py +12 -3
- localstack/packages/api.py +9 -8
- localstack/packages/core.py +2 -2
- localstack/packages/plugins.py +0 -8
- localstack/runtime/init.py +1 -1
- localstack/services/apigateway/helpers.py +5 -9
- localstack/services/apigateway/legacy/provider.py +85 -12
- localstack/services/apigateway/next_gen/execute_api/integrations/aws.py +3 -0
- localstack/services/apigateway/next_gen/execute_api/integrations/http.py +3 -3
- localstack/services/apigateway/next_gen/execute_api/test_invoke.py +50 -6
- localstack/services/apigateway/next_gen/provider.py +5 -0
- localstack/services/apigateway/patches.py +0 -9
- localstack/services/cloudformation/engine/entities.py +12 -1
- localstack/services/cloudformation/engine/v2/change_set_model.py +0 -3
- localstack/services/cloudformation/engine/v2/change_set_model_describer.py +14 -0
- localstack/services/cloudformation/engine/v2/change_set_model_executor.py +13 -15
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +118 -24
- localstack/services/cloudformation/engine/v2/change_set_model_transform.py +4 -1
- localstack/services/cloudformation/engine/v2/change_set_model_validator.py +5 -14
- localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +1 -0
- localstack/services/cloudformation/engine/v2/resolving.py +6 -4
- localstack/services/cloudformation/engine/yaml_parser.py +9 -2
- localstack/services/cloudformation/provider.py +2 -2
- localstack/services/cloudformation/resource_provider.py +5 -1
- localstack/services/cloudformation/resources.py +24149 -0
- localstack/services/cloudformation/v2/entities.py +6 -3
- localstack/services/cloudformation/v2/provider.py +178 -33
- localstack/services/cloudformation/v2/types.py +8 -4
- localstack/services/cloudwatch/provider_v2.py +25 -28
- localstack/services/dynamodb/packages.py +2 -1
- localstack/services/dynamodb/provider.py +42 -0
- localstack/services/dynamodb/v2/provider.py +42 -0
- localstack/services/ecr/resource_providers/aws_ecr_repository.py +5 -2
- localstack/services/es/provider.py +2 -2
- localstack/services/events/event_rule_engine.py +31 -13
- localstack/services/events/models.py +4 -5
- localstack/services/events/target.py +17 -9
- localstack/services/iam/provider.py +11 -116
- localstack/services/iam/resources/policy_simulator.py +133 -0
- localstack/services/kinesis/models.py +15 -2
- localstack/services/kinesis/packages.py +1 -1
- localstack/services/kinesis/provider.py +77 -0
- localstack/services/kms/models.py +34 -4
- localstack/services/kms/provider.py +107 -21
- localstack/services/lambda_/api_utils.py +3 -1
- localstack/services/lambda_/invocation/internal_sqs_queue.py +5 -9
- localstack/services/lambda_/packages.py +1 -1
- localstack/services/lambda_/provider.py +1 -1
- localstack/services/lambda_/runtimes.py +8 -3
- localstack/services/logs/provider.py +36 -19
- localstack/services/moto.py +2 -1
- localstack/services/opensearch/cluster.py +15 -7
- localstack/services/opensearch/packages.py +26 -7
- localstack/services/opensearch/provider.py +6 -1
- localstack/services/opensearch/versions.py +56 -7
- localstack/services/s3/constants.py +5 -2
- localstack/services/s3/cors.py +4 -4
- localstack/services/s3/notifications.py +1 -1
- localstack/services/s3/presigned_url.py +27 -43
- localstack/services/s3/provider.py +68 -12
- localstack/services/s3/utils.py +42 -11
- localstack/services/ses/provider.py +16 -7
- localstack/services/sns/constants.py +7 -1
- localstack/services/sns/v2/models.py +190 -0
- localstack/services/sns/v2/provider.py +992 -2
- localstack/services/sns/v2/utils.py +138 -0
- localstack/services/sqs/developer_api.py +205 -0
- localstack/services/sqs/models.py +79 -13
- localstack/services/sqs/provider.py +8 -309
- localstack/services/sqs/query_api.py +1 -1
- localstack/services/sqs/utils.py +121 -2
- localstack/services/stepfunctions/asl/jsonata/jsonata.py +1 -1
- localstack/testing/aws/cloudformation_utils.py +1 -1
- localstack/testing/pytest/cloudformation/fixtures.py +3 -3
- localstack/testing/pytest/container.py +4 -5
- localstack/testing/pytest/fixtures.py +20 -19
- localstack/testing/pytest/in_memory_localstack.py +0 -4
- localstack/testing/pytest/marking.py +13 -4
- localstack/testing/pytest/stepfunctions/utils.py +4 -3
- localstack/testing/pytest/util.py +1 -1
- localstack/testing/pytest/validation_tracking.py +1 -2
- localstack/testing/snapshots/transformer_utility.py +7 -0
- localstack/testing/testselection/matching.py +0 -1
- localstack/utils/analytics/events.py +2 -2
- localstack/utils/analytics/metadata.py +1 -2
- localstack/utils/analytics/metrics/counter.py +6 -8
- localstack/utils/analytics/publisher.py +1 -2
- localstack/utils/analytics/service_request_aggregator.py +2 -2
- localstack/utils/archives.py +11 -11
- localstack/utils/aws/arns.py +17 -9
- localstack/utils/aws/aws_responses.py +7 -7
- localstack/utils/aws/aws_stack.py +2 -3
- localstack/utils/aws/client_types.py +0 -8
- localstack/utils/aws/message_forwarding.py +1 -2
- localstack/utils/aws/request_context.py +4 -5
- localstack/utils/batch_policy.py +3 -3
- localstack/utils/bootstrap.py +7 -7
- localstack/utils/catalog/catalog.py +139 -0
- localstack/utils/catalog/catalog_loader.py +119 -0
- localstack/utils/catalog/common.py +58 -0
- localstack/utils/catalog/plugins.py +28 -0
- localstack/utils/cloudwatch/cloudwatch_util.py +5 -5
- localstack/utils/collections.py +7 -8
- localstack/utils/config_listener.py +1 -1
- localstack/utils/container_networking.py +2 -3
- localstack/utils/container_utils/container_client.py +115 -131
- localstack/utils/container_utils/docker_cmd_client.py +42 -42
- localstack/utils/container_utils/docker_sdk_client.py +63 -62
- localstack/utils/crypto.py +109 -0
- localstack/utils/diagnose.py +2 -3
- localstack/utils/docker_utils.py +3 -4
- localstack/utils/files.py +31 -7
- localstack/utils/functions.py +3 -2
- localstack/utils/http.py +4 -5
- localstack/utils/json.py +19 -5
- localstack/utils/kinesis/kinesis_connector.py +2 -1
- localstack/utils/net.py +6 -6
- localstack/utils/no_exit_argument_parser.py +2 -2
- localstack/utils/numbers.py +9 -2
- localstack/utils/objects.py +6 -5
- localstack/utils/patch.py +2 -1
- localstack/utils/run.py +10 -9
- localstack/utils/scheduler.py +11 -11
- localstack/utils/server/tcp_proxy.py +2 -2
- localstack/utils/serving.py +2 -3
- localstack/utils/strings.py +10 -11
- localstack/utils/sync.py +126 -1
- localstack/utils/tagging.py +1 -4
- localstack/utils/testutil.py +5 -4
- localstack/utils/threads.py +2 -2
- localstack/utils/time.py +11 -3
- localstack/utils/urls.py +1 -3
- localstack/version.py +2 -2
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/METADATA +19 -13
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/RECORD +203 -199
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/entry_points.txt +4 -2
- localstack_core-4.10.1.dev42.dist-info/plux.json +1 -0
- localstack/packages/terraform.py +0 -46
- localstack/services/cloudformation/deploy.html +0 -144
- localstack/services/cloudformation/deploy_ui.py +0 -47
- localstack/services/cloudformation/plugins.py +0 -12
- localstack_core-4.7.1.dev139.dist-info/plux.json +0 -1
- {localstack_core-4.7.1.dev139.data → localstack_core-4.10.1.dev42.data}/scripts/localstack +0 -0
- {localstack_core-4.7.1.dev139.data → localstack_core-4.10.1.dev42.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.7.1.dev139.data → localstack_core-4.10.1.dev42.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/WHEEL +0 -0
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/top_level.txt +0 -0
localstack/config.py
CHANGED
|
@@ -10,7 +10,7 @@ import time
|
|
|
10
10
|
import warnings
|
|
11
11
|
from collections import defaultdict
|
|
12
12
|
from collections.abc import Mapping
|
|
13
|
-
from typing import Any,
|
|
13
|
+
from typing import Any, TypeVar
|
|
14
14
|
|
|
15
15
|
from localstack import constants
|
|
16
16
|
from localstack.constants import (
|
|
@@ -19,6 +19,7 @@ from localstack.constants import (
|
|
|
19
19
|
DEFAULT_VOLUME_DIR,
|
|
20
20
|
ENV_INTERNAL_TEST_COLLECT_METRIC,
|
|
21
21
|
ENV_INTERNAL_TEST_RUN,
|
|
22
|
+
ENV_INTERNAL_TEST_STORE_METRICS_IN_LOCALSTACK,
|
|
22
23
|
FALSE_STRINGS,
|
|
23
24
|
LOCALHOST,
|
|
24
25
|
LOCALHOST_IP,
|
|
@@ -208,13 +209,13 @@ class Directories:
|
|
|
208
209
|
return str(self.__dict__)
|
|
209
210
|
|
|
210
211
|
|
|
211
|
-
def eval_log_type(env_var_name: str) ->
|
|
212
|
+
def eval_log_type(env_var_name: str) -> str | bool:
|
|
212
213
|
"""Get the log type from environment variable"""
|
|
213
214
|
ls_log = os.environ.get(env_var_name, "").lower().strip()
|
|
214
215
|
return ls_log if ls_log in LOG_LEVELS else False
|
|
215
216
|
|
|
216
217
|
|
|
217
|
-
def parse_boolean_env(env_var_name: str) ->
|
|
218
|
+
def parse_boolean_env(env_var_name: str) -> bool | None:
|
|
218
219
|
"""Parse the value of the given env variable and return True/False, or None if it is not a boolean value."""
|
|
219
220
|
value = os.environ.get(env_var_name, "").lower().strip()
|
|
220
221
|
if value in TRUE_STRINGS:
|
|
@@ -649,7 +650,7 @@ class UniqueHostAndPortList(list[HostAndPort]):
|
|
|
649
650
|
- Identical identical hosts and ports are de-duped
|
|
650
651
|
"""
|
|
651
652
|
|
|
652
|
-
def __init__(self, iterable:
|
|
653
|
+
def __init__(self, iterable: list[HostAndPort] | None = None):
|
|
653
654
|
super().__init__(iterable or [])
|
|
654
655
|
self._ensure_unique()
|
|
655
656
|
|
|
@@ -1451,6 +1452,11 @@ def is_collect_metrics_mode() -> bool:
|
|
|
1451
1452
|
return is_env_true(ENV_INTERNAL_TEST_COLLECT_METRIC)
|
|
1452
1453
|
|
|
1453
1454
|
|
|
1455
|
+
def store_test_metrics_in_local_filesystem() -> bool:
|
|
1456
|
+
"""Returns True if test metrics should be stored in the local filesystem (instead of the system that runs pytest)."""
|
|
1457
|
+
return is_env_true(ENV_INTERNAL_TEST_STORE_METRICS_IN_LOCALSTACK)
|
|
1458
|
+
|
|
1459
|
+
|
|
1454
1460
|
def collect_config_items() -> list[tuple[str, Any]]:
|
|
1455
1461
|
"""Returns a list of key-value tuples of LocalStack configuration values."""
|
|
1456
1462
|
none = object() # sentinel object
|
|
@@ -1503,10 +1509,10 @@ def get_protocol() -> str:
|
|
|
1503
1509
|
|
|
1504
1510
|
|
|
1505
1511
|
def external_service_url(
|
|
1506
|
-
host:
|
|
1507
|
-
port:
|
|
1508
|
-
protocol:
|
|
1509
|
-
subdomains:
|
|
1512
|
+
host: str | None = None,
|
|
1513
|
+
port: int | None = None,
|
|
1514
|
+
protocol: str | None = None,
|
|
1515
|
+
subdomains: str | None = None,
|
|
1510
1516
|
) -> str:
|
|
1511
1517
|
"""Returns a service URL (e.g., SQS queue URL) to an external client (e.g., boto3) potentially running on another
|
|
1512
1518
|
machine than LocalStack. The configurations LOCALSTACK_HOST and USE_SSL can customize these returned URLs.
|
|
@@ -1523,10 +1529,10 @@ def external_service_url(
|
|
|
1523
1529
|
|
|
1524
1530
|
|
|
1525
1531
|
def internal_service_url(
|
|
1526
|
-
host:
|
|
1527
|
-
port:
|
|
1528
|
-
protocol:
|
|
1529
|
-
subdomains:
|
|
1532
|
+
host: str | None = None,
|
|
1533
|
+
port: int | None = None,
|
|
1534
|
+
protocol: str | None = None,
|
|
1535
|
+
subdomains: str | None = None,
|
|
1530
1536
|
) -> str:
|
|
1531
1537
|
"""Returns a service URL for internal use within LocalStack (i.e., same host).
|
|
1532
1538
|
The configuration USE_SSL can customize these returned URLs but LOCALSTACK_HOST has no effect.
|
localstack/constants.py
CHANGED
|
@@ -80,6 +80,10 @@ ENV_INTERNAL_TEST_RUN = "LOCALSTACK_INTERNAL_TEST_RUN"
|
|
|
80
80
|
# environment variable name to tag collect metrics during a test run
|
|
81
81
|
ENV_INTERNAL_TEST_COLLECT_METRIC = "LOCALSTACK_INTERNAL_TEST_COLLECT_METRIC"
|
|
82
82
|
|
|
83
|
+
# environment variable name to indicate that metrics should be stored within the container
|
|
84
|
+
ENV_INTERNAL_TEST_STORE_METRICS_IN_LOCALSTACK = "LOCALSTACK_INTERNAL_TEST_METRICS_IN_LOCALSTACK"
|
|
85
|
+
ENV_INTERNAL_TEST_STORE_METRICS_PATH = "LOCALSTACK_INTERNAL_TEST_STORE_METRICS_PATH"
|
|
86
|
+
|
|
83
87
|
# environment variable that flags whether pro was activated. do not use it for security purposes!
|
|
84
88
|
ENV_PRO_ACTIVATED = "PRO_ACTIVATED"
|
|
85
89
|
|
|
@@ -102,32 +106,6 @@ FALSE_STRINGS = ("0", "false", "False")
|
|
|
102
106
|
# strings with valid log levels for LS_LOG
|
|
103
107
|
LOG_LEVELS = ("trace-internal", "trace", "debug", "info", "warn", "error", "warning")
|
|
104
108
|
|
|
105
|
-
# the version of elasticsearch that is pre-seeded into the base image (sync with Dockerfile.base)
|
|
106
|
-
ELASTICSEARCH_DEFAULT_VERSION = "Elasticsearch_7.10"
|
|
107
|
-
# See https://docs.aws.amazon.com/ja_jp/elasticsearch-service/latest/developerguide/aes-supported-plugins.html
|
|
108
|
-
ELASTICSEARCH_PLUGIN_LIST = [
|
|
109
|
-
"analysis-icu",
|
|
110
|
-
"ingest-attachment",
|
|
111
|
-
"analysis-kuromoji",
|
|
112
|
-
"mapper-murmur3",
|
|
113
|
-
"mapper-size",
|
|
114
|
-
"analysis-phonetic",
|
|
115
|
-
"analysis-smartcn",
|
|
116
|
-
"analysis-stempel",
|
|
117
|
-
"analysis-ukrainian",
|
|
118
|
-
]
|
|
119
|
-
# Default ES modules to exclude (save apprx 66MB in the final image)
|
|
120
|
-
ELASTICSEARCH_DELETE_MODULES = ["ingest-geoip"]
|
|
121
|
-
|
|
122
|
-
# the version of opensearch which is used by default
|
|
123
|
-
OPENSEARCH_DEFAULT_VERSION = "OpenSearch_2.11"
|
|
124
|
-
|
|
125
|
-
# See https://docs.aws.amazon.com/opensearch-service/latest/developerguide/supported-plugins.html
|
|
126
|
-
OPENSEARCH_PLUGIN_LIST = [
|
|
127
|
-
"ingest-attachment",
|
|
128
|
-
"analysis-kuromoji",
|
|
129
|
-
]
|
|
130
|
-
|
|
131
109
|
# API endpoint for analytics events
|
|
132
110
|
API_ENDPOINT = os.environ.get("API_ENDPOINT") or "https://api.localstack.cloud/v1"
|
|
133
111
|
# new analytics API endpoint
|
|
@@ -171,9 +149,6 @@ DEFAULT_DEVELOP_PORT = 5678
|
|
|
171
149
|
DEFAULT_BUCKET_MARKER_LOCAL = "hot-reload"
|
|
172
150
|
LEGACY_DEFAULT_BUCKET_MARKER_LOCAL = "__local__"
|
|
173
151
|
|
|
174
|
-
# user that starts the opensearch process if the current user is root
|
|
175
|
-
OS_USER_OPENSEARCH = "localstack"
|
|
176
|
-
|
|
177
152
|
# output string that indicates that the stack is ready
|
|
178
153
|
READY_MARKER_OUTPUT = "Ready."
|
|
179
154
|
|
|
@@ -9,6 +9,7 @@ EDGE_SERVICE_NODE_PORT = 30066
|
|
|
9
9
|
NODE_PORT_START = 30010
|
|
10
10
|
SERVICE_PORT_START = 4510
|
|
11
11
|
NUMBER_OF_SERVICE_PORTS = 50
|
|
12
|
+
EDGE_SERVICE_DNS_PORT = 31053
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
@dataclasses.dataclass
|
|
@@ -32,7 +33,7 @@ def generate_mount_points(
|
|
|
32
33
|
|
|
33
34
|
# container paths
|
|
34
35
|
target_path = "/opt/code/localstack/"
|
|
35
|
-
venv_path = os.path.join(target_path, ".venv", "lib", "python3.
|
|
36
|
+
venv_path = os.path.join(target_path, ".venv", "lib", "python3.13", "site-packages")
|
|
36
37
|
|
|
37
38
|
# Community code
|
|
38
39
|
if pro:
|
|
@@ -144,7 +145,12 @@ def generate_mount_points(
|
|
|
144
145
|
return mount_points
|
|
145
146
|
|
|
146
147
|
|
|
147
|
-
def generate_k8s_cluster_config(
|
|
148
|
+
def generate_k8s_cluster_config(
|
|
149
|
+
mount_points: list[MountPoint],
|
|
150
|
+
port: int = 4566,
|
|
151
|
+
expose_dns: bool = False,
|
|
152
|
+
dns_port: int = 53,
|
|
153
|
+
):
|
|
148
154
|
volumes = [
|
|
149
155
|
{
|
|
150
156
|
"volume": f"{mount_point.host_path}:{mount_point.node_path}",
|
|
@@ -177,6 +183,16 @@ def generate_k8s_cluster_config(mount_points: list[MountPoint], port: int = 4566
|
|
|
177
183
|
},
|
|
178
184
|
]
|
|
179
185
|
|
|
186
|
+
if expose_dns:
|
|
187
|
+
ports.append(
|
|
188
|
+
{
|
|
189
|
+
"nodeFilters": [
|
|
190
|
+
"server:0",
|
|
191
|
+
],
|
|
192
|
+
"port": f"{dns_port}:{EDGE_SERVICE_DNS_PORT}",
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
|
|
180
196
|
config = {
|
|
181
197
|
"apiVersion": "k3d.io/v1alpha5",
|
|
182
198
|
"kind": "Simple",
|
|
@@ -279,7 +295,10 @@ def generate_k8s_helm_overrides(
|
|
|
279
295
|
"end": SERVICE_PORT_START + NUMBER_OF_SERVICE_PORTS,
|
|
280
296
|
"nodePortStart": NODE_PORT_START,
|
|
281
297
|
},
|
|
282
|
-
"dnsService":
|
|
298
|
+
"dnsService": {
|
|
299
|
+
"enabled": True,
|
|
300
|
+
"nodePort": EDGE_SERVICE_DNS_PORT,
|
|
301
|
+
},
|
|
283
302
|
}
|
|
284
303
|
overrides = {
|
|
285
304
|
"debug": True,
|
|
@@ -355,6 +374,18 @@ def print_file(content: dict, file_name: str):
|
|
|
355
374
|
help="Port to expose from the kubernetes node",
|
|
356
375
|
type=click.IntRange(0, 65535),
|
|
357
376
|
)
|
|
377
|
+
@click.option(
|
|
378
|
+
"--expose-dns",
|
|
379
|
+
is_flag=True,
|
|
380
|
+
default=False,
|
|
381
|
+
help="Expose DNS port from the kubernetes node.",
|
|
382
|
+
)
|
|
383
|
+
@click.option(
|
|
384
|
+
"--dns-port",
|
|
385
|
+
default=53,
|
|
386
|
+
help="DNS port to expose from the kubernetes node. It is applied only if --expose-dns is set.",
|
|
387
|
+
type=click.IntRange(0, 65535),
|
|
388
|
+
)
|
|
358
389
|
@click.argument("command", nargs=-1, required=False)
|
|
359
390
|
def run(
|
|
360
391
|
pro: bool = None,
|
|
@@ -367,13 +398,17 @@ def run(
|
|
|
367
398
|
command: str = None,
|
|
368
399
|
env: list[str] = None,
|
|
369
400
|
port: int = None,
|
|
401
|
+
expose_dns: bool = False,
|
|
402
|
+
dns_port: int = 53,
|
|
370
403
|
):
|
|
371
404
|
"""
|
|
372
405
|
A tool for localstack developers to generate the kubernetes cluster configuration file and the overrides to mount the localstack code into the cluster.
|
|
373
406
|
"""
|
|
374
407
|
mount_points = generate_mount_points(pro, mount_moto, mount_entrypoints)
|
|
375
408
|
|
|
376
|
-
config = generate_k8s_cluster_config(
|
|
409
|
+
config = generate_k8s_cluster_config(
|
|
410
|
+
mount_points, port=port, expose_dns=expose_dns, dns_port=dns_port
|
|
411
|
+
)
|
|
377
412
|
|
|
378
413
|
overrides = generate_k8s_helm_overrides(mount_points, pro=pro, env=env)
|
|
379
414
|
|
localstack/dev/run/paths.py
CHANGED
|
@@ -68,7 +68,7 @@ class ContainerPaths:
|
|
|
68
68
|
"""Important paths in the container"""
|
|
69
69
|
|
|
70
70
|
project_dir: str = "/opt/code/localstack"
|
|
71
|
-
site_packages_target_dir: str = "/opt/code/localstack/.venv/lib/python3.
|
|
71
|
+
site_packages_target_dir: str = "/opt/code/localstack/.venv/lib/python3.13/site-packages"
|
|
72
72
|
docker_entrypoint: str = "/usr/local/bin/docker-entrypoint.sh"
|
|
73
73
|
localstack_supervisor: str = "/usr/local/bin/localstack-supervisor"
|
|
74
74
|
localstack_source_dir: str
|
localstack/dns/plugins.py
CHANGED
|
@@ -11,8 +11,12 @@ DNS_SHUTDOWN_PRIORITY = -30
|
|
|
11
11
|
"""Make sure the DNS server is shut down after the ON_AFTER_SERVICE_SHUTDOWN_HANDLERS, which in turn is after
|
|
12
12
|
SERVICE_SHUTDOWN_PRIORITY. Currently this value needs to be less than -20"""
|
|
13
13
|
|
|
14
|
+
DNS_START_PRIORITY = 20
|
|
15
|
+
"""Make sure the DNS server is started before the pro activation, to ensure proper DNS resolution for the activate call,
|
|
16
|
+
if the resolv.conf is set to localhost from outside the container"""
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
|
|
19
|
+
@hooks.on_infra_start(priority=DNS_START_PRIORITY)
|
|
16
20
|
def start_dns_server():
|
|
17
21
|
try:
|
|
18
22
|
from localstack.dns import server
|
localstack/dns/server.py
CHANGED
|
@@ -445,13 +445,22 @@ class Resolver(DnsServerProtocol):
|
|
|
445
445
|
return True
|
|
446
446
|
return False
|
|
447
447
|
|
|
448
|
+
def _find_matching_aliases(self, question: DNSQuestion) -> list[AliasTarget] | None:
|
|
449
|
+
"""
|
|
450
|
+
Find aliases matching the question, supporting wildcards.
|
|
451
|
+
"""
|
|
452
|
+
qlabel = DNSLabel(to_bytes(question.qname))
|
|
453
|
+
qtype = RecordType[QTYPE[question.qtype]]
|
|
454
|
+
for (label, rtype), targets in self.aliases.items():
|
|
455
|
+
if rtype == qtype and qlabel.matchWildcard(label):
|
|
456
|
+
return targets
|
|
457
|
+
return None
|
|
458
|
+
|
|
448
459
|
def _resolve_alias(
|
|
449
460
|
self, request: DNSRecord, reply: DNSRecord, client_address: ClientAddress
|
|
450
461
|
) -> bool:
|
|
451
462
|
if request.q.qtype in (QTYPE.A, QTYPE.AAAA, QTYPE.CNAME):
|
|
452
|
-
|
|
453
|
-
# check if we have aliases defined for our given qname/qtype pair
|
|
454
|
-
if aliases := self.aliases.get(key):
|
|
463
|
+
if aliases := self._find_matching_aliases(request.q):
|
|
455
464
|
for alias in aliases:
|
|
456
465
|
# if there is no health check, or the healthcheck is successful, we will consider this alias
|
|
457
466
|
# take the first alias passing this check
|
localstack/packages/api.py
CHANGED
|
@@ -3,10 +3,11 @@ import functools
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
5
|
from collections import defaultdict
|
|
6
|
+
from collections.abc import Callable
|
|
6
7
|
from enum import Enum
|
|
7
8
|
from inspect import getmodule
|
|
8
|
-
from threading import RLock
|
|
9
|
-
from typing import Any,
|
|
9
|
+
from threading import Lock, RLock
|
|
10
|
+
from typing import Any, Generic, ParamSpec, TypeVar
|
|
10
11
|
|
|
11
12
|
from plux import Plugin, PluginManager, PluginSpec # type: ignore
|
|
12
13
|
|
|
@@ -56,7 +57,7 @@ class PackageInstaller(abc.ABC):
|
|
|
56
57
|
multiple versions).
|
|
57
58
|
"""
|
|
58
59
|
|
|
59
|
-
def __init__(self, name: str, version: str, install_lock:
|
|
60
|
+
def __init__(self, name: str, version: str, install_lock: Lock | None = None):
|
|
60
61
|
"""
|
|
61
62
|
:param name: technical package name, f.e. "opensearch"
|
|
62
63
|
:param version: version of the package to install
|
|
@@ -70,7 +71,7 @@ class PackageInstaller(abc.ABC):
|
|
|
70
71
|
self.install_lock = install_lock or RLock()
|
|
71
72
|
self._setup_for_target: dict[InstallTarget, bool] = defaultdict(lambda: False)
|
|
72
73
|
|
|
73
|
-
def install(self, target:
|
|
74
|
+
def install(self, target: InstallTarget | None = None) -> None:
|
|
74
75
|
"""
|
|
75
76
|
Performs the package installation.
|
|
76
77
|
|
|
@@ -210,7 +211,7 @@ class Package(abc.ABC, Generic[T]):
|
|
|
210
211
|
"""
|
|
211
212
|
return self.get_installer(version).get_installed_dir()
|
|
212
213
|
|
|
213
|
-
def install(self, version: str | None = None, target:
|
|
214
|
+
def install(self, version: str | None = None, target: InstallTarget | None = None) -> None:
|
|
214
215
|
"""
|
|
215
216
|
Installs the package in the given version in the preferred target location.
|
|
216
217
|
:param version: version of the package to install. If None, the default version of the package will be used.
|
|
@@ -274,7 +275,7 @@ class MultiPackageInstaller(PackageInstaller):
|
|
|
274
275
|
assert len(package_installer) > 0
|
|
275
276
|
self.package_installer = package_installer
|
|
276
277
|
|
|
277
|
-
def install(self, target:
|
|
278
|
+
def install(self, target: InstallTarget | None = None) -> None:
|
|
278
279
|
"""
|
|
279
280
|
Installs the different packages this installer is composed of.
|
|
280
281
|
|
|
@@ -356,7 +357,7 @@ class PackagesPluginManager(PluginManager[PackagesPlugin]): # type: ignore[misc
|
|
|
356
357
|
)
|
|
357
358
|
|
|
358
359
|
def get_packages(
|
|
359
|
-
self, package_names: list[str], version:
|
|
360
|
+
self, package_names: list[str], version: str | None = None
|
|
360
361
|
) -> list[Package[PackageInstaller]]:
|
|
361
362
|
# Plugin names are unique, but there could be multiple packages with the same name in different scopes
|
|
362
363
|
plugin_specs_per_name = defaultdict(list)
|
|
@@ -390,7 +391,7 @@ T2 = TypeVar("T2")
|
|
|
390
391
|
def package(
|
|
391
392
|
name: str | None = None,
|
|
392
393
|
scope: str = "community",
|
|
393
|
-
should_load:
|
|
394
|
+
should_load: Callable[[], bool] | None = None,
|
|
394
395
|
) -> Callable[[Callable[[], Package[Any] | list[Package[Any]]]], PluginSpec]:
|
|
395
396
|
"""
|
|
396
397
|
Decorator for marking methods that create Package instances as a PackagePlugin.
|
localstack/packages/core.py
CHANGED
|
@@ -4,7 +4,7 @@ import re
|
|
|
4
4
|
from abc import ABC
|
|
5
5
|
from functools import lru_cache
|
|
6
6
|
from sys import version_info
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any
|
|
8
8
|
|
|
9
9
|
import requests
|
|
10
10
|
|
|
@@ -238,7 +238,7 @@ class NodePackageInstaller(ExecutableInstaller):
|
|
|
238
238
|
self,
|
|
239
239
|
package_name: str,
|
|
240
240
|
version: str,
|
|
241
|
-
package_spec:
|
|
241
|
+
package_spec: str | None = None,
|
|
242
242
|
main_module: str = "main.js",
|
|
243
243
|
):
|
|
244
244
|
"""
|
localstack/packages/plugins.py
CHANGED
|
@@ -5,14 +5,6 @@ from localstack.packages.api import Package, package
|
|
|
5
5
|
if TYPE_CHECKING:
|
|
6
6
|
from localstack.packages.ffmpeg import FfmpegPackageInstaller
|
|
7
7
|
from localstack.packages.java import JavaPackageInstaller
|
|
8
|
-
from localstack.packages.terraform import TerraformPackageInstaller
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@package(name="terraform")
|
|
12
|
-
def terraform_package() -> Package["TerraformPackageInstaller"]:
|
|
13
|
-
from .terraform import terraform_package
|
|
14
|
-
|
|
15
|
-
return terraform_package
|
|
16
8
|
|
|
17
9
|
|
|
18
10
|
@package(name="ffmpeg")
|
localstack/runtime/init.py
CHANGED
|
@@ -89,7 +89,7 @@ class ShellScriptRunner(ScriptRunner):
|
|
|
89
89
|
suffixes = [".sh"]
|
|
90
90
|
|
|
91
91
|
def run(self, path: str) -> None:
|
|
92
|
-
exit_code = subprocess.call(args=[]
|
|
92
|
+
exit_code = subprocess.call(args=[path])
|
|
93
93
|
if exit_code != 0:
|
|
94
94
|
raise OSError(f"Script {path} returned a non-zero exit code {exit_code}")
|
|
95
95
|
|
|
@@ -23,7 +23,7 @@ from localstack.aws.api.apigateway import (
|
|
|
23
23
|
IntegrationType,
|
|
24
24
|
Model,
|
|
25
25
|
NotFoundException,
|
|
26
|
-
|
|
26
|
+
PutMode,
|
|
27
27
|
RequestValidator,
|
|
28
28
|
)
|
|
29
29
|
from localstack.constants import (
|
|
@@ -39,8 +39,7 @@ from localstack.services.apigateway.models import (
|
|
|
39
39
|
apigateway_stores,
|
|
40
40
|
)
|
|
41
41
|
from localstack.utils import common
|
|
42
|
-
from localstack.utils.
|
|
43
|
-
from localstack.utils.strings import short_uid, to_bytes, to_str
|
|
42
|
+
from localstack.utils.strings import short_uid, to_bytes
|
|
44
43
|
from localstack.utils.urls import localstack_host
|
|
45
44
|
|
|
46
45
|
LOG = logging.getLogger(__name__)
|
|
@@ -472,11 +471,9 @@ def add_documentation_parts(rest_api_container, documentation):
|
|
|
472
471
|
|
|
473
472
|
|
|
474
473
|
def import_api_from_openapi_spec(
|
|
475
|
-
rest_api: MotoRestAPI, context: RequestContext,
|
|
474
|
+
rest_api: MotoRestAPI, context: RequestContext, open_api_spec: dict, mode: PutMode
|
|
476
475
|
) -> tuple[MotoRestAPI, list[str]]:
|
|
477
476
|
"""Import an API from an OpenAPI spec document"""
|
|
478
|
-
body = parse_json_or_yaml(to_str(request["body"].read()))
|
|
479
|
-
|
|
480
477
|
warnings = []
|
|
481
478
|
|
|
482
479
|
# TODO There is an issue with the botocore specs so the parameters doesn't get populated as it should
|
|
@@ -484,15 +481,14 @@ def import_api_from_openapi_spec(
|
|
|
484
481
|
# query_params = request.get("parameters") or {}
|
|
485
482
|
query_params: dict = context.request.values.to_dict()
|
|
486
483
|
|
|
487
|
-
resolved_schema = resolve_references(copy.deepcopy(
|
|
484
|
+
resolved_schema = resolve_references(copy.deepcopy(open_api_spec), rest_api_id=rest_api.id)
|
|
488
485
|
account_id = context.account_id
|
|
489
486
|
region_name = context.region
|
|
490
487
|
|
|
491
488
|
# TODO:
|
|
492
|
-
# 1.
|
|
489
|
+
# 1. properly apply the mode (overwrite or merge)
|
|
493
490
|
# for now, it only considers it for the binaryMediaTypes
|
|
494
491
|
# 2. validate the document type, "swagger" or "openapi"
|
|
495
|
-
mode = request.get("mode", "merge")
|
|
496
492
|
|
|
497
493
|
rest_api.version = (
|
|
498
494
|
str(version) if (version := resolved_schema.get("info", {}).get("version")) else None
|
|
@@ -323,8 +323,18 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
323
323
|
tags: MapOfStringToString = None,
|
|
324
324
|
**kwargs,
|
|
325
325
|
) -> ApiKey:
|
|
326
|
+
if name and len(name) > 1024:
|
|
327
|
+
raise BadRequestException("Invalid API Key name, can be at most 1024 characters.")
|
|
328
|
+
if value:
|
|
329
|
+
if len(value) > 128:
|
|
330
|
+
raise BadRequestException("API Key value exceeds maximum size of 128 characters")
|
|
331
|
+
elif len(value) < 20:
|
|
332
|
+
raise BadRequestException("API Key value should be at least 20 characters")
|
|
333
|
+
if description and len(description) > 125000:
|
|
334
|
+
raise BadRequestException("Invalid API Key description specified.")
|
|
326
335
|
api_key = call_moto(context)
|
|
327
|
-
|
|
336
|
+
if name == "":
|
|
337
|
+
api_key.pop("name", None)
|
|
328
338
|
# transform array of stage keys [{'restApiId': '0iscapk09u', 'stageName': 'dev'}] into
|
|
329
339
|
# array of strings ['0iscapk09u/dev']
|
|
330
340
|
stage_keys = api_key.get("stageKeys", [])
|
|
@@ -466,11 +476,19 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
466
476
|
|
|
467
477
|
@handler("PutRestApi", expand=False)
|
|
468
478
|
def put_rest_api(self, context: RequestContext, request: PutRestApiRequest) -> RestApi:
|
|
479
|
+
body_data = request["body"].read()
|
|
480
|
+
try:
|
|
481
|
+
openapi_spec = parse_json_or_yaml(to_str(body_data))
|
|
482
|
+
except Exception:
|
|
483
|
+
raise BadRequestException("Invalid OpenAPI input.")
|
|
469
484
|
# TODO: take into account the mode: overwrite or merge
|
|
470
485
|
# the default is now `merge`, but we are removing everything
|
|
471
486
|
rest_api = get_moto_rest_api(context, request["restApiId"])
|
|
472
487
|
rest_api, warnings = import_api_from_openapi_spec(
|
|
473
|
-
rest_api,
|
|
488
|
+
rest_api,
|
|
489
|
+
context=context,
|
|
490
|
+
open_api_spec=openapi_spec,
|
|
491
|
+
mode=request.get("mode") or PutMode.merge,
|
|
474
492
|
)
|
|
475
493
|
|
|
476
494
|
rest_api.root_resource_id = get_moto_rest_api_root_resource(rest_api)
|
|
@@ -1502,7 +1520,10 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
1502
1520
|
**kwargs,
|
|
1503
1521
|
) -> DocumentationPartIds:
|
|
1504
1522
|
body_data = body.read()
|
|
1505
|
-
|
|
1523
|
+
try:
|
|
1524
|
+
openapi_spec = parse_json_or_yaml(to_str(body_data))
|
|
1525
|
+
except Exception:
|
|
1526
|
+
raise BadRequestException("Unable to build importer with provided input.")
|
|
1506
1527
|
|
|
1507
1528
|
rest_api_container = get_rest_api_container(context, rest_api_id=rest_api_id)
|
|
1508
1529
|
|
|
@@ -2002,7 +2023,11 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
2002
2023
|
body_data = body.read()
|
|
2003
2024
|
|
|
2004
2025
|
# create rest api
|
|
2005
|
-
|
|
2026
|
+
try:
|
|
2027
|
+
openapi_spec = parse_json_or_yaml(to_str(body_data))
|
|
2028
|
+
except Exception:
|
|
2029
|
+
raise BadRequestException("Invalid OpenAPI input.")
|
|
2030
|
+
|
|
2006
2031
|
create_api_request = CreateRestApiRequest(name=openapi_spec.get("info").get("title"))
|
|
2007
2032
|
create_api_context = create_custom_context(
|
|
2008
2033
|
context,
|
|
@@ -2043,17 +2068,39 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
2043
2068
|
**kwargs,
|
|
2044
2069
|
) -> Integration:
|
|
2045
2070
|
try:
|
|
2046
|
-
|
|
2047
|
-
except
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
raise
|
|
2071
|
+
moto_rest_api = get_moto_rest_api(context, rest_api_id)
|
|
2072
|
+
except NotFoundException:
|
|
2073
|
+
raise NotFoundException("Invalid Resource identifier specified")
|
|
2074
|
+
|
|
2075
|
+
if not (moto_resource := moto_rest_api.resources.get(resource_id)):
|
|
2076
|
+
raise NotFoundException("Invalid Resource identifier specified")
|
|
2077
|
+
|
|
2078
|
+
if not (moto_method := moto_resource.resource_methods.get(http_method)):
|
|
2079
|
+
raise NotFoundException("Invalid Method identifier specified")
|
|
2080
|
+
|
|
2081
|
+
if not moto_method.method_integration:
|
|
2082
|
+
raise NotFoundException("Invalid Integration identifier specified")
|
|
2083
|
+
|
|
2084
|
+
response: Integration = call_moto(context)
|
|
2052
2085
|
|
|
2053
2086
|
if integration_responses := response.get("integrationResponses"):
|
|
2054
2087
|
for integration_response in integration_responses.values():
|
|
2055
2088
|
remove_empty_attributes_from_integration_response(integration_response)
|
|
2056
2089
|
|
|
2090
|
+
if response.get("connectionType") == "VPC_LINK":
|
|
2091
|
+
# FIXME: this is hacky to workaround moto not saving the VPC Link `connectionId`
|
|
2092
|
+
# only do this internal check of Moto if the integration is of VPC_LINK type
|
|
2093
|
+
moto_rest_api = get_moto_rest_api(context=context, rest_api_id=rest_api_id)
|
|
2094
|
+
try:
|
|
2095
|
+
method = moto_rest_api.resources[resource_id].resource_methods[http_method]
|
|
2096
|
+
integration = method.method_integration
|
|
2097
|
+
if connection_id := getattr(integration, "connection_id", None):
|
|
2098
|
+
response["connectionId"] = connection_id
|
|
2099
|
+
|
|
2100
|
+
except (AttributeError, KeyError):
|
|
2101
|
+
# this error should have been caught by `call_moto`
|
|
2102
|
+
pass
|
|
2103
|
+
|
|
2057
2104
|
return response
|
|
2058
2105
|
|
|
2059
2106
|
def put_integration(
|
|
@@ -2108,6 +2155,7 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
2108
2155
|
moto_request.setdefault("timeoutInMillis", 29000)
|
|
2109
2156
|
if integration_type in (IntegrationType.HTTP, IntegrationType.HTTP_PROXY):
|
|
2110
2157
|
moto_request.setdefault("connectionType", ConnectionType.INTERNET)
|
|
2158
|
+
|
|
2111
2159
|
response = call_moto_with_request(context, moto_request)
|
|
2112
2160
|
remove_empty_attributes_from_integration(integration=response)
|
|
2113
2161
|
|
|
@@ -2115,6 +2163,13 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
2115
2163
|
if integration_type == "MOCK":
|
|
2116
2164
|
response.pop("uri", None)
|
|
2117
2165
|
|
|
2166
|
+
# TODO: moto does not save the connection_id
|
|
2167
|
+
elif moto_request.get("connectionType") == "VPC_LINK":
|
|
2168
|
+
connection_id = moto_request.get("connectionId", "")
|
|
2169
|
+
# attach the connection id to the moto object
|
|
2170
|
+
method.method_integration.connection_id = connection_id
|
|
2171
|
+
response["connectionId"] = connection_id
|
|
2172
|
+
|
|
2118
2173
|
return response
|
|
2119
2174
|
|
|
2120
2175
|
def update_integration(
|
|
@@ -2136,6 +2191,7 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
2136
2191
|
raise NotFoundException("Invalid Integration identifier specified")
|
|
2137
2192
|
|
|
2138
2193
|
integration = method.method_integration
|
|
2194
|
+
# TODO: validate the patch operations
|
|
2139
2195
|
patch_api_gateway_entity(integration, patch_operations)
|
|
2140
2196
|
|
|
2141
2197
|
# fix data types
|
|
@@ -2144,8 +2200,12 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
2144
2200
|
if skip_verification := (integration.tls_config or {}).get("insecureSkipVerification"):
|
|
2145
2201
|
integration.tls_config["insecureSkipVerification"] = str_to_bool(skip_verification)
|
|
2146
2202
|
|
|
2147
|
-
|
|
2148
|
-
|
|
2203
|
+
response: Integration = integration.to_json()
|
|
2204
|
+
|
|
2205
|
+
if connection_id := getattr(integration, "connection_id", None):
|
|
2206
|
+
response["connectionId"] = connection_id
|
|
2207
|
+
|
|
2208
|
+
return response
|
|
2149
2209
|
|
|
2150
2210
|
def delete_integration(
|
|
2151
2211
|
self,
|
|
@@ -2393,6 +2453,10 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
2393
2453
|
for api_key in api_keys:
|
|
2394
2454
|
api_key.pop("value")
|
|
2395
2455
|
|
|
2456
|
+
if limit is not None:
|
|
2457
|
+
if limit < 1 or limit > 500:
|
|
2458
|
+
limit = None
|
|
2459
|
+
|
|
2396
2460
|
item_list = PaginatedList(api_keys)
|
|
2397
2461
|
|
|
2398
2462
|
def token_generator(item):
|
|
@@ -2417,6 +2481,14 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
2417
2481
|
patch_operations: ListOfPatchOperation = None,
|
|
2418
2482
|
**kwargs,
|
|
2419
2483
|
) -> ApiKey:
|
|
2484
|
+
for patch_op in patch_operations:
|
|
2485
|
+
if patch_op["path"] not in ("/description", "/enabled", "/name", "/customerId"):
|
|
2486
|
+
raise BadRequestException(
|
|
2487
|
+
f"Invalid patch path '{patch_op['path']}' specified for op '{patch_op['op']}'. Must be one of: [/description, /enabled, /name, /customerId]"
|
|
2488
|
+
)
|
|
2489
|
+
|
|
2490
|
+
if patch_op["path"] == "/description" and len(patch_op["value"]) > 125000:
|
|
2491
|
+
raise BadRequestException("Invalid API Key description specified.")
|
|
2420
2492
|
response: ApiKey = call_moto(context)
|
|
2421
2493
|
if "value" in response:
|
|
2422
2494
|
response.pop("value", None)
|
|
@@ -2977,6 +3049,7 @@ def create_custom_context(
|
|
|
2977
3049
|
ctx = create_aws_request_context(
|
|
2978
3050
|
service_name=context.service.service_name,
|
|
2979
3051
|
action=action,
|
|
3052
|
+
protocol=context.service.protocol,
|
|
2980
3053
|
parameters=parameters,
|
|
2981
3054
|
region=context.region,
|
|
2982
3055
|
)
|
|
@@ -211,6 +211,9 @@ class RestApiAwsIntegration(RestApiIntegration):
|
|
|
211
211
|
action = parsed_uri["path"]
|
|
212
212
|
|
|
213
213
|
if target := self.get_action_service_target(service_name, action):
|
|
214
|
+
# TODO: properly implement the auto-`Content-Type` headers depending on the service protocol
|
|
215
|
+
# e.g. `x-amz-json-1.0` for DynamoDB
|
|
216
|
+
# this is needed to properly support multi-protocol
|
|
214
217
|
headers["X-Amz-Target"] = target
|
|
215
218
|
|
|
216
219
|
query_params["Action"] = action
|