localstack-core 4.10.1.dev7__py3-none-any.whl → 4.10.1.dev42__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- localstack/aws/api/acm/__init__.py +122 -122
- localstack/aws/api/apigateway/__init__.py +560 -559
- localstack/aws/api/cloudcontrol/__init__.py +63 -63
- localstack/aws/api/cloudformation/__init__.py +1040 -969
- localstack/aws/api/cloudwatch/__init__.py +375 -375
- localstack/aws/api/config/__init__.py +784 -786
- localstack/aws/api/dynamodb/__init__.py +753 -759
- localstack/aws/api/dynamodbstreams/__init__.py +74 -74
- localstack/aws/api/ec2/__init__.py +8901 -8818
- localstack/aws/api/es/__init__.py +453 -453
- localstack/aws/api/events/__init__.py +552 -552
- localstack/aws/api/firehose/__init__.py +541 -543
- localstack/aws/api/iam/__init__.py +639 -572
- localstack/aws/api/kinesis/__init__.py +235 -147
- localstack/aws/api/kms/__init__.py +340 -336
- localstack/aws/api/lambda_/__init__.py +574 -573
- localstack/aws/api/logs/__init__.py +676 -675
- localstack/aws/api/opensearch/__init__.py +814 -785
- localstack/aws/api/pipes/__init__.py +336 -336
- localstack/aws/api/redshift/__init__.py +1188 -1166
- localstack/aws/api/resource_groups/__init__.py +175 -175
- localstack/aws/api/resourcegroupstaggingapi/__init__.py +67 -67
- localstack/aws/api/route53/__init__.py +254 -254
- localstack/aws/api/route53resolver/__init__.py +396 -396
- localstack/aws/api/s3/__init__.py +1350 -1349
- localstack/aws/api/s3control/__init__.py +594 -594
- localstack/aws/api/scheduler/__init__.py +118 -118
- localstack/aws/api/secretsmanager/__init__.py +193 -193
- localstack/aws/api/ses/__init__.py +227 -227
- localstack/aws/api/sns/__init__.py +115 -115
- localstack/aws/api/sqs/__init__.py +100 -100
- localstack/aws/api/ssm/__init__.py +1977 -1971
- localstack/aws/api/stepfunctions/__init__.py +323 -323
- localstack/aws/api/sts/__init__.py +90 -66
- localstack/aws/api/support/__init__.py +112 -112
- localstack/aws/api/swf/__init__.py +378 -386
- localstack/aws/api/transcribe/__init__.py +425 -425
- localstack/aws/handlers/service.py +11 -1
- localstack/aws/protocol/parser.py +1 -1
- localstack/aws/scaffold.py +15 -17
- localstack/cli/localstack.py +6 -1
- localstack/dev/kubernetes/__main__.py +38 -3
- localstack/services/apigateway/helpers.py +5 -9
- localstack/services/apigateway/legacy/provider.py +32 -9
- localstack/services/apigateway/patches.py +0 -9
- localstack/services/cloudformation/provider.py +2 -2
- localstack/services/cloudformation/v2/provider.py +6 -6
- localstack/services/kinesis/packages.py +1 -1
- localstack/services/kms/models.py +34 -4
- localstack/services/kms/provider.py +93 -16
- localstack/services/lambda_/api_utils.py +3 -1
- localstack/services/lambda_/packages.py +1 -1
- localstack/services/lambda_/provider.py +1 -1
- localstack/services/lambda_/runtimes.py +8 -3
- localstack/services/logs/provider.py +36 -19
- localstack/services/s3/provider.py +1 -1
- localstack/services/sns/v2/models.py +24 -1
- localstack/services/sns/v2/provider.py +144 -12
- localstack/services/sns/v2/utils.py +8 -0
- localstack/services/sqs/models.py +37 -10
- localstack/testing/snapshots/transformer_utility.py +2 -0
- localstack/testing/testselection/matching.py +0 -1
- localstack/utils/aws/client_types.py +0 -8
- localstack/utils/catalog/catalog_loader.py +111 -3
- localstack/utils/crypto.py +109 -0
- localstack/version.py +2 -2
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.10.1.dev42.dist-info}/METADATA +6 -5
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.10.1.dev42.dist-info}/RECORD +76 -76
- localstack_core-4.10.1.dev42.dist-info/plux.json +1 -0
- localstack_core-4.10.1.dev7.dist-info/plux.json +0 -1
- {localstack_core-4.10.1.dev7.data → localstack_core-4.10.1.dev42.data}/scripts/localstack +0 -0
- {localstack_core-4.10.1.dev7.data → localstack_core-4.10.1.dev42.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.10.1.dev7.data → localstack_core-4.10.1.dev42.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.10.1.dev42.dist-info}/WHEEL +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.10.1.dev42.dist-info}/entry_points.txt +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.10.1.dev42.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.10.1.dev7.dist-info → localstack_core-4.10.1.dev42.dist-info}/top_level.txt +0 -0
|
@@ -7,6 +7,7 @@ from collections import defaultdict
|
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
9
|
from botocore.model import OperationModel, ServiceModel
|
|
10
|
+
from plux.core.plugin import PluginDisabled
|
|
10
11
|
|
|
11
12
|
from localstack import config
|
|
12
13
|
from localstack.http import Response
|
|
@@ -222,7 +223,16 @@ class ServiceExceptionSerializer(ExceptionHandler):
|
|
|
222
223
|
operation = context.service.operation_model(context.service.operation_names[0])
|
|
223
224
|
msg = f"exception while calling {service_name} with unknown operation: {message}"
|
|
224
225
|
|
|
225
|
-
|
|
226
|
+
# Check for license restricted plugin message and set status code to 501
|
|
227
|
+
if (
|
|
228
|
+
isinstance(exception, PluginDisabled)
|
|
229
|
+
and "not part of the active license agreement"
|
|
230
|
+
in str(getattr(exception, "reason", "")).lower()
|
|
231
|
+
):
|
|
232
|
+
status_code = 501
|
|
233
|
+
msg = f"exception while calling {service_name}.{operation.name}: {str(getattr(exception, 'reason', ''))}"
|
|
234
|
+
else:
|
|
235
|
+
status_code = 501 if config.FAIL_FAST else 500
|
|
226
236
|
|
|
227
237
|
error = CommonServiceException(
|
|
228
238
|
"InternalError", msg, status_code=status_code
|
|
@@ -1032,7 +1032,7 @@ class BaseCBORRequestParser(RequestParser, ABC):
|
|
|
1032
1032
|
return method(stream, additional_info)
|
|
1033
1033
|
else:
|
|
1034
1034
|
raise ProtocolParserError(
|
|
1035
|
-
f"Unsupported
|
|
1035
|
+
f"Unsupported initial byte found for data item- "
|
|
1036
1036
|
f"Major type:{major_type}, Additional info: "
|
|
1037
1037
|
f"{additional_info}"
|
|
1038
1038
|
)
|
localstack/aws/scaffold.py
CHANGED
|
@@ -188,9 +188,7 @@ class ShapeNode:
|
|
|
188
188
|
if member in self.shape.required_members:
|
|
189
189
|
output.write(f" {member}: IO[{q}{to_valid_python_name(shape.name)}{q}]\n")
|
|
190
190
|
else:
|
|
191
|
-
output.write(
|
|
192
|
-
f" {member}: Optional[IO[{q}{to_valid_python_name(shape.name)}{q}]]\n"
|
|
193
|
-
)
|
|
191
|
+
output.write(f" {member}: {q}IO[{to_valid_python_name(shape.name)}] | None{q}\n")
|
|
194
192
|
del remaining_members[member]
|
|
195
193
|
# render the streaming payload first
|
|
196
194
|
if self.is_response and self.response_operation.has_streaming_output:
|
|
@@ -199,25 +197,26 @@ class ShapeNode:
|
|
|
199
197
|
shape_name = to_valid_python_name(shape.name)
|
|
200
198
|
if member in self.shape.required_members:
|
|
201
199
|
output.write(
|
|
202
|
-
f" {member}:
|
|
200
|
+
f" {member}: {q}{shape_name} | IO[{shape_name}] | Iterable[{shape_name}]{q}\n"
|
|
203
201
|
)
|
|
204
202
|
else:
|
|
205
203
|
output.write(
|
|
206
|
-
f" {member}:
|
|
204
|
+
f" {member}: {q}{shape_name} | IO[{shape_name}] | Iterable[{shape_name}] | None{q}\n"
|
|
207
205
|
)
|
|
208
206
|
del remaining_members[member]
|
|
209
207
|
|
|
210
208
|
for k, v in remaining_members.items():
|
|
209
|
+
shape_name = to_valid_python_name(v.name)
|
|
211
210
|
if k in self.shape.required_members:
|
|
212
211
|
if v.serialization.get("eventstream"):
|
|
213
|
-
output.write(f" {k}: Iterator[{q}{
|
|
212
|
+
output.write(f" {k}: Iterator[{q}{shape_name}{q}]\n")
|
|
214
213
|
else:
|
|
215
|
-
output.write(f" {k}: {q}{
|
|
214
|
+
output.write(f" {k}: {q}{shape_name}{q}\n")
|
|
216
215
|
else:
|
|
217
216
|
if v.serialization.get("eventstream"):
|
|
218
|
-
output.write(f" {k}: Iterator[{q}{
|
|
217
|
+
output.write(f" {k}: Iterator[{q}{shape_name}{q}]\n")
|
|
219
218
|
else:
|
|
220
|
-
output.write(f" {k}:
|
|
219
|
+
output.write(f" {k}: {q}{shape_name} | None{q}\n")
|
|
221
220
|
|
|
222
221
|
def _print_as_typed_dict(self, output, doc=True, quote_types=False):
|
|
223
222
|
name = to_valid_python_name(self.shape.name)
|
|
@@ -236,7 +235,7 @@ class ShapeNode:
|
|
|
236
235
|
if v.serialization.get("eventstream"):
|
|
237
236
|
output.write(f' "{k}": Iterator[{q}{member_name}{q}],\n')
|
|
238
237
|
else:
|
|
239
|
-
output.write(f' "{k}":
|
|
238
|
+
output.write(f' "{k}": {q}{member_name} | None{q},\n')
|
|
240
239
|
output.write("}, total=False)")
|
|
241
240
|
|
|
242
241
|
def print_shape_doc(self, output, shape):
|
|
@@ -256,11 +255,11 @@ class ShapeNode:
|
|
|
256
255
|
self._print_structure_declaration(output, doc, quote_types)
|
|
257
256
|
elif isinstance(shape, ListShape):
|
|
258
257
|
output.write(
|
|
259
|
-
f"{to_valid_python_name(shape.name)} =
|
|
258
|
+
f"{to_valid_python_name(shape.name)} = list[{q}{to_valid_python_name(shape.member.name)}{q}]"
|
|
260
259
|
)
|
|
261
260
|
elif isinstance(shape, MapShape):
|
|
262
261
|
output.write(
|
|
263
|
-
f"{to_valid_python_name(shape.name)} =
|
|
262
|
+
f"{to_valid_python_name(shape.name)} = dict[{q}{to_valid_python_name(shape.key.name)}{q}, {q}{to_valid_python_name(shape.value.name)}{q}]"
|
|
264
263
|
)
|
|
265
264
|
elif isinstance(shape, StringShape):
|
|
266
265
|
if shape.enum:
|
|
@@ -316,9 +315,8 @@ class ShapeNode:
|
|
|
316
315
|
def generate_service_types(output, service: ServiceModel, doc=True):
|
|
317
316
|
output.write("from datetime import datetime\n")
|
|
318
317
|
output.write("from enum import StrEnum\n")
|
|
319
|
-
output.write(
|
|
320
|
-
|
|
321
|
-
)
|
|
318
|
+
output.write("from typing import IO, TypedDict\n")
|
|
319
|
+
output.write("from collections.abc import Iterable, Iterator\n")
|
|
322
320
|
output.write("\n")
|
|
323
321
|
output.write(
|
|
324
322
|
"from localstack.aws.api import handler, RequestContext, ServiceException, ServiceRequest"
|
|
@@ -372,8 +370,8 @@ def generate_service_api(output, service: ServiceModel, doc=True):
|
|
|
372
370
|
|
|
373
371
|
output.write(f"class {class_name}:\n")
|
|
374
372
|
output.write("\n")
|
|
375
|
-
output.write(f' service = "{service.service_name}"\n')
|
|
376
|
-
output.write(f' version = "{service.api_version}"\n')
|
|
373
|
+
output.write(f' service: str = "{service.service_name}"\n')
|
|
374
|
+
output.write(f' version: str = "{service.api_version}"\n')
|
|
377
375
|
for op_name in service.operation_names:
|
|
378
376
|
operation: OperationModel = service.operation_model(op_name)
|
|
379
377
|
|
localstack/cli/localstack.py
CHANGED
|
@@ -433,7 +433,7 @@ def _print_service_table(services: dict[str, str]) -> None:
|
|
|
433
433
|
|
|
434
434
|
@localstack.command(name="start", short_help="Start LocalStack")
|
|
435
435
|
@click.option("--docker", is_flag=True, help="Start LocalStack in a docker container [default]")
|
|
436
|
-
@click.option("--host", is_flag=True, help="Start LocalStack directly on the host")
|
|
436
|
+
@click.option("--host", is_flag=True, help="Start LocalStack directly on the host", deprecated=True)
|
|
437
437
|
@click.option("--no-banner", is_flag=True, help="Disable LocalStack banner", default=False)
|
|
438
438
|
@click.option(
|
|
439
439
|
"-d", "--detached", is_flag=True, help="Start LocalStack in the background", default=False
|
|
@@ -532,6 +532,11 @@ def cmd_start(
|
|
|
532
532
|
console.log("starting LocalStack in Docker mode :whale:")
|
|
533
533
|
|
|
534
534
|
if host:
|
|
535
|
+
console.log(
|
|
536
|
+
"Warning: Starting LocalStack in host mode from the CLI is deprecated and will be removed soon. Please use the default Docker mode instead.",
|
|
537
|
+
style="bold red",
|
|
538
|
+
)
|
|
539
|
+
|
|
535
540
|
# call hooks to prepare host
|
|
536
541
|
bootstrap.prepare_host(console)
|
|
537
542
|
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -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
|
|
@@ -476,11 +476,19 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
476
476
|
|
|
477
477
|
@handler("PutRestApi", expand=False)
|
|
478
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.")
|
|
479
484
|
# TODO: take into account the mode: overwrite or merge
|
|
480
485
|
# the default is now `merge`, but we are removing everything
|
|
481
486
|
rest_api = get_moto_rest_api(context, request["restApiId"])
|
|
482
487
|
rest_api, warnings = import_api_from_openapi_spec(
|
|
483
|
-
rest_api,
|
|
488
|
+
rest_api,
|
|
489
|
+
context=context,
|
|
490
|
+
open_api_spec=openapi_spec,
|
|
491
|
+
mode=request.get("mode") or PutMode.merge,
|
|
484
492
|
)
|
|
485
493
|
|
|
486
494
|
rest_api.root_resource_id = get_moto_rest_api_root_resource(rest_api)
|
|
@@ -1512,7 +1520,10 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
1512
1520
|
**kwargs,
|
|
1513
1521
|
) -> DocumentationPartIds:
|
|
1514
1522
|
body_data = body.read()
|
|
1515
|
-
|
|
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.")
|
|
1516
1527
|
|
|
1517
1528
|
rest_api_container = get_rest_api_container(context, rest_api_id=rest_api_id)
|
|
1518
1529
|
|
|
@@ -2012,7 +2023,11 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
2012
2023
|
body_data = body.read()
|
|
2013
2024
|
|
|
2014
2025
|
# create rest api
|
|
2015
|
-
|
|
2026
|
+
try:
|
|
2027
|
+
openapi_spec = parse_json_or_yaml(to_str(body_data))
|
|
2028
|
+
except Exception:
|
|
2029
|
+
raise BadRequestException("Invalid OpenAPI input.")
|
|
2030
|
+
|
|
2016
2031
|
create_api_request = CreateRestApiRequest(name=openapi_spec.get("info").get("title"))
|
|
2017
2032
|
create_api_context = create_custom_context(
|
|
2018
2033
|
context,
|
|
@@ -2053,12 +2068,20 @@ class ApigatewayProvider(ApigatewayApi, ServiceLifecycleHook):
|
|
|
2053
2068
|
**kwargs,
|
|
2054
2069
|
) -> Integration:
|
|
2055
2070
|
try:
|
|
2056
|
-
|
|
2057
|
-
except
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
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)
|
|
2062
2085
|
|
|
2063
2086
|
if integration_responses := response.get("integrationResponses"):
|
|
2064
2087
|
for integration_response in integration_responses.values():
|
|
@@ -5,7 +5,6 @@ import logging
|
|
|
5
5
|
from moto.apigateway import models as apigateway_models
|
|
6
6
|
from moto.apigateway.exceptions import (
|
|
7
7
|
DeploymentNotFoundException,
|
|
8
|
-
NoIntegrationDefined,
|
|
9
8
|
RestAPINotFound,
|
|
10
9
|
StageStillActive,
|
|
11
10
|
)
|
|
@@ -113,14 +112,6 @@ def apply_patches():
|
|
|
113
112
|
)
|
|
114
113
|
return result
|
|
115
114
|
|
|
116
|
-
# patch integration error responses
|
|
117
|
-
@patch(apigateway_models.Resource.get_integration)
|
|
118
|
-
def apigateway_models_resource_get_integration(fn, self, method_type):
|
|
119
|
-
resource_method = self.resource_methods.get(method_type, {})
|
|
120
|
-
if not resource_method.method_integration:
|
|
121
|
-
raise NoIntegrationDefined()
|
|
122
|
-
return resource_method.method_integration
|
|
123
|
-
|
|
124
115
|
@patch(apigateway_models.RestAPI.to_dict)
|
|
125
116
|
def apigateway_models_rest_api_to_dict(fn, self):
|
|
126
117
|
resp = fn(self)
|
|
@@ -952,8 +952,8 @@ class CloudformationProvider(CloudformationApi):
|
|
|
952
952
|
def describe_stack_events(
|
|
953
953
|
self,
|
|
954
954
|
context: RequestContext,
|
|
955
|
-
stack_name: StackName
|
|
956
|
-
next_token: NextToken = None,
|
|
955
|
+
stack_name: StackName,
|
|
956
|
+
next_token: NextToken | None = None,
|
|
957
957
|
**kwargs,
|
|
958
958
|
) -> DescribeStackEventsOutput:
|
|
959
959
|
if stack_name is None:
|
|
@@ -135,15 +135,15 @@ SSM_PARAMETER_TYPE_RE = re.compile(
|
|
|
135
135
|
|
|
136
136
|
|
|
137
137
|
def is_stack_arn(stack_name_or_id: str) -> bool:
|
|
138
|
-
return ARN_STACK_REGEX.match(stack_name_or_id) is not None
|
|
138
|
+
return stack_name_or_id and ARN_STACK_REGEX.match(stack_name_or_id) is not None
|
|
139
139
|
|
|
140
140
|
|
|
141
141
|
def is_changeset_arn(change_set_name_or_id: str) -> bool:
|
|
142
|
-
return ARN_CHANGESET_REGEX.match(change_set_name_or_id) is not None
|
|
142
|
+
return change_set_name_or_id and ARN_CHANGESET_REGEX.match(change_set_name_or_id) is not None
|
|
143
143
|
|
|
144
144
|
|
|
145
145
|
def is_stack_set_arn(stack_set_name_or_id: str) -> bool:
|
|
146
|
-
return ARN_STACK_SET_REGEX.match(stack_set_name_or_id) is not None
|
|
146
|
+
return stack_set_name_or_id and ARN_STACK_SET_REGEX.match(stack_set_name_or_id) is not None
|
|
147
147
|
|
|
148
148
|
|
|
149
149
|
class StackNotFoundError(ValidationError):
|
|
@@ -1349,8 +1349,8 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
1349
1349
|
def describe_stack_events(
|
|
1350
1350
|
self,
|
|
1351
1351
|
context: RequestContext,
|
|
1352
|
-
stack_name: StackName
|
|
1353
|
-
next_token: NextToken = None,
|
|
1352
|
+
stack_name: StackName,
|
|
1353
|
+
next_token: NextToken | None = None,
|
|
1354
1354
|
**kwargs,
|
|
1355
1355
|
) -> DescribeStackEventsOutput:
|
|
1356
1356
|
if not stack_name:
|
|
@@ -1388,7 +1388,7 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
1388
1388
|
stack_name, message_override=f"Stack with id {stack_name} does not exist"
|
|
1389
1389
|
)
|
|
1390
1390
|
else:
|
|
1391
|
-
raise
|
|
1391
|
+
raise ValidationError("StackName is required if ChangeSetName is not specified.")
|
|
1392
1392
|
|
|
1393
1393
|
if template_stage == TemplateStage.Processed and "Transform" in stack.template_body:
|
|
1394
1394
|
template_body = json.dumps(stack.processed_template)
|
|
@@ -7,7 +7,7 @@ from localstack.packages import InstallTarget, Package
|
|
|
7
7
|
from localstack.packages.core import GitHubReleaseInstaller, NodePackageInstaller
|
|
8
8
|
from localstack.packages.java import JavaInstallerMixin, java_package
|
|
9
9
|
|
|
10
|
-
_KINESIS_MOCK_VERSION = os.environ.get("KINESIS_MOCK_VERSION") or "0.
|
|
10
|
+
_KINESIS_MOCK_VERSION = os.environ.get("KINESIS_MOCK_VERSION") or "0.5.1"
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class KinesisMockEngine(StrEnum):
|
|
@@ -173,6 +173,7 @@ class KmsCryptoKey:
|
|
|
173
173
|
public_key: bytes | None
|
|
174
174
|
private_key: bytes | None
|
|
175
175
|
key_material: bytes
|
|
176
|
+
pending_key_material: bytes | None
|
|
176
177
|
key_spec: str
|
|
177
178
|
|
|
178
179
|
@staticmethod
|
|
@@ -217,6 +218,7 @@ class KmsCryptoKey:
|
|
|
217
218
|
def __init__(self, key_spec: str, key_material: bytes | None = None):
|
|
218
219
|
self.private_key = None
|
|
219
220
|
self.public_key = None
|
|
221
|
+
self.pending_key_material = None
|
|
220
222
|
# Technically, key_material, being a symmetric encryption key, is only relevant for
|
|
221
223
|
# key_spec == SYMMETRIC_DEFAULT.
|
|
222
224
|
# But LocalStack uses symmetric encryption with this key_material even for other specs. Asymmetric keys are
|
|
@@ -248,8 +250,9 @@ class KmsCryptoKey:
|
|
|
248
250
|
self._serialize_key(key)
|
|
249
251
|
|
|
250
252
|
def load_key_material(self, material: bytes):
|
|
251
|
-
if self.key_spec
|
|
252
|
-
|
|
253
|
+
if self.key_spec == KeySpec.SYMMETRIC_DEFAULT:
|
|
254
|
+
self.pending_key_material = material
|
|
255
|
+
elif self.key_spec in [
|
|
253
256
|
KeySpec.HMAC_224,
|
|
254
257
|
KeySpec.HMAC_256,
|
|
255
258
|
KeySpec.HMAC_384,
|
|
@@ -323,9 +326,28 @@ class KmsKey:
|
|
|
323
326
|
# remove the _custom_key_material_ tag from the tags to not readily expose the custom key material
|
|
324
327
|
del self.tags[TAG_KEY_CUSTOM_KEY_MATERIAL]
|
|
325
328
|
self.crypto_key = KmsCryptoKey(self.metadata.get("KeySpec"), custom_key_material)
|
|
329
|
+
self._internal_key_id = uuid.uuid4()
|
|
330
|
+
|
|
331
|
+
# The KMS implementation always provides a crypto key with key material which doesn't suit scenarios where a
|
|
332
|
+
# KMS Key may have no key material e.g. for external keys. Don't expose the CurrentKeyMaterialId in those cases.
|
|
333
|
+
if custom_key_material or (
|
|
334
|
+
self.metadata["Origin"] == "AWS_KMS"
|
|
335
|
+
and self.metadata["KeySpec"] == KeySpec.SYMMETRIC_DEFAULT
|
|
336
|
+
):
|
|
337
|
+
self.metadata["CurrentKeyMaterialId"] = self.generate_key_material_id(
|
|
338
|
+
self.crypto_key.key_material
|
|
339
|
+
)
|
|
340
|
+
|
|
326
341
|
self.rotation_period_in_days = 365
|
|
327
342
|
self.next_rotation_date = None
|
|
328
343
|
|
|
344
|
+
def generate_key_material_id(self, key_material: bytes) -> str:
|
|
345
|
+
# The KeyMaterialId depends on the key material and the KeyId. Use an internal ID to prevent brute forcing
|
|
346
|
+
# the value of the key material from the public KeyId and KeyMaterialId.
|
|
347
|
+
# https://docs.aws.amazon.com/kms/latest/APIReference/API_ImportKeyMaterial.html
|
|
348
|
+
key_material_id_hex = uuid.uuid5(self._internal_key_id, key_material).hex
|
|
349
|
+
return str(key_material_id_hex) * 2
|
|
350
|
+
|
|
329
351
|
def calculate_and_set_arn(self, account_id, region):
|
|
330
352
|
self.metadata["Arn"] = kms_key_arn(self.metadata.get("KeyId"), account_id, region)
|
|
331
353
|
|
|
@@ -746,8 +768,16 @@ class KmsKey:
|
|
|
746
768
|
f"The on-demand rotations limit has been reached for the given keyId. "
|
|
747
769
|
f"No more on-demand rotations can be performed for this key: {self.metadata['Arn']}"
|
|
748
770
|
)
|
|
749
|
-
self.
|
|
750
|
-
|
|
771
|
+
current_key_material = self.crypto_key.key_material
|
|
772
|
+
pending_key_material = self.crypto_key.pending_key_material
|
|
773
|
+
|
|
774
|
+
self.previous_keys.append(current_key_material)
|
|
775
|
+
|
|
776
|
+
# If there is no pending material stored on the key, then key material will be generated.
|
|
777
|
+
self.crypto_key = KmsCryptoKey(KeySpec.SYMMETRIC_DEFAULT, pending_key_material)
|
|
778
|
+
self.metadata["CurrentKeyMaterialId"] = self.generate_key_material_id(
|
|
779
|
+
self.crypto_key.key_material
|
|
780
|
+
)
|
|
751
781
|
|
|
752
782
|
|
|
753
783
|
class KmsGrant:
|