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
|
@@ -25,7 +25,7 @@ from localstack.aws.api.cloudformation import (
|
|
|
25
25
|
Parameter as ApiParameter,
|
|
26
26
|
)
|
|
27
27
|
from localstack.services.cloudformation.engine.entities import (
|
|
28
|
-
|
|
28
|
+
StackIdentifierV2,
|
|
29
29
|
)
|
|
30
30
|
from localstack.services.cloudformation.engine.v2.change_set_model import (
|
|
31
31
|
ChangeType,
|
|
@@ -41,6 +41,7 @@ class Stack:
|
|
|
41
41
|
description: str | None
|
|
42
42
|
parameters: list[ApiParameter]
|
|
43
43
|
change_set_id: str | None
|
|
44
|
+
change_set_ids: set[str]
|
|
44
45
|
status: StackStatus
|
|
45
46
|
status_reason: StackStatusReason | None
|
|
46
47
|
stack_id: str
|
|
@@ -49,6 +50,7 @@ class Stack:
|
|
|
49
50
|
events: list[StackEvent]
|
|
50
51
|
capabilities: list[Capability]
|
|
51
52
|
enable_termination_protection: bool
|
|
53
|
+
template: dict | None
|
|
52
54
|
processed_template: dict | None
|
|
53
55
|
template_body: str | None
|
|
54
56
|
tags: list[Tag]
|
|
@@ -72,11 +74,12 @@ class Stack:
|
|
|
72
74
|
self.region_name = region_name
|
|
73
75
|
self.status = initial_status
|
|
74
76
|
self.status_reason = None
|
|
75
|
-
self.change_set_ids =
|
|
77
|
+
self.change_set_ids = set()
|
|
76
78
|
self.creation_time = datetime.now(tz=UTC)
|
|
77
79
|
self.deletion_time = None
|
|
78
80
|
self.change_set_id = None
|
|
79
81
|
self.enable_termination_protection = False
|
|
82
|
+
self.template = None
|
|
80
83
|
self.processed_template = None
|
|
81
84
|
self.template_body = None
|
|
82
85
|
self.tags = tags or []
|
|
@@ -85,7 +88,7 @@ class Stack:
|
|
|
85
88
|
self.parameters = request_payload.get("Parameters", [])
|
|
86
89
|
self.stack_id = arns.cloudformation_stack_arn(
|
|
87
90
|
self.stack_name,
|
|
88
|
-
stack_id=
|
|
91
|
+
stack_id=StackIdentifierV2(
|
|
89
92
|
account_id=self.account_id, region=self.region_name, stack_name=self.stack_name
|
|
90
93
|
).generate(tags=request_payload.get("Tags")),
|
|
91
94
|
account_id=self.account_id,
|
|
@@ -15,6 +15,7 @@ from localstack.aws.api.cloudformation import (
|
|
|
15
15
|
ChangeSetNameOrId,
|
|
16
16
|
ChangeSetNotFoundException,
|
|
17
17
|
ChangeSetStatus,
|
|
18
|
+
ChangeSetSummary,
|
|
18
19
|
ChangeSetType,
|
|
19
20
|
ClientRequestToken,
|
|
20
21
|
CreateChangeSetInput,
|
|
@@ -46,6 +47,7 @@ from localstack.aws.api.cloudformation import (
|
|
|
46
47
|
IncludePropertyValues,
|
|
47
48
|
InsufficientCapabilitiesException,
|
|
48
49
|
InvalidChangeSetStatusException,
|
|
50
|
+
ListChangeSetsOutput,
|
|
49
51
|
ListExportsOutput,
|
|
50
52
|
ListStackResourcesOutput,
|
|
51
53
|
ListStacksOutput,
|
|
@@ -118,9 +120,10 @@ from localstack.services.cloudformation.v2.entities import (
|
|
|
118
120
|
StackInstance,
|
|
119
121
|
StackSet,
|
|
120
122
|
)
|
|
121
|
-
from localstack.services.cloudformation.v2.types import EngineParameter
|
|
123
|
+
from localstack.services.cloudformation.v2.types import EngineParameter, engine_parameter_value
|
|
122
124
|
from localstack.services.plugins import ServiceLifecycleHook
|
|
123
125
|
from localstack.utils.collections import select_attributes
|
|
126
|
+
from localstack.utils.numbers import is_number
|
|
124
127
|
from localstack.utils.strings import short_uid
|
|
125
128
|
from localstack.utils.threads import start_worker_thread
|
|
126
129
|
|
|
@@ -132,15 +135,15 @@ SSM_PARAMETER_TYPE_RE = re.compile(
|
|
|
132
135
|
|
|
133
136
|
|
|
134
137
|
def is_stack_arn(stack_name_or_id: str) -> bool:
|
|
135
|
-
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
|
|
136
139
|
|
|
137
140
|
|
|
138
141
|
def is_changeset_arn(change_set_name_or_id: str) -> bool:
|
|
139
|
-
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
|
|
140
143
|
|
|
141
144
|
|
|
142
145
|
def is_stack_set_arn(stack_set_name_or_id: str) -> bool:
|
|
143
|
-
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
|
|
144
147
|
|
|
145
148
|
|
|
146
149
|
class StackNotFoundError(ValidationError):
|
|
@@ -234,14 +237,18 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
234
237
|
issue_url = "?".join([base, urlencode(query_args)])
|
|
235
238
|
LOG.info(
|
|
236
239
|
"You have opted in to the new CloudFormation deployment engine. "
|
|
237
|
-
"You can opt in to using the old engine by setting PROVIDER_OVERRIDE_CLOUDFORMATION=legacy. "
|
|
240
|
+
"You can opt in to using the old engine by setting PROVIDER_OVERRIDE_CLOUDFORMATION=engine-legacy. "
|
|
238
241
|
"If you experience issues, please submit a bug report at this URL: %s",
|
|
239
242
|
issue_url,
|
|
240
243
|
)
|
|
241
244
|
|
|
242
245
|
@staticmethod
|
|
243
246
|
def _resolve_parameters(
|
|
244
|
-
template: dict | None,
|
|
247
|
+
template: dict | None,
|
|
248
|
+
parameters: dict | None,
|
|
249
|
+
account_id: str,
|
|
250
|
+
region_name: str,
|
|
251
|
+
before_parameters: dict | None,
|
|
245
252
|
) -> dict[str, EngineParameter]:
|
|
246
253
|
template_parameters = template.get("Parameters", {})
|
|
247
254
|
resolved_parameters = {}
|
|
@@ -256,6 +263,12 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
256
263
|
no_echo=parameter.get("NoEcho"),
|
|
257
264
|
)
|
|
258
265
|
|
|
266
|
+
# validate the type
|
|
267
|
+
if parameter["Type"] == "Number" and not is_number(
|
|
268
|
+
engine_parameter_value(resolved_parameter)
|
|
269
|
+
):
|
|
270
|
+
raise ValidationError(f"Parameter '{name}' must be a number.")
|
|
271
|
+
|
|
259
272
|
# TODO: support other parameter types
|
|
260
273
|
if match := SSM_PARAMETER_TYPE_RE.match(parameter["Type"]):
|
|
261
274
|
inner_type = match.group("innertype")
|
|
@@ -276,10 +289,25 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
276
289
|
resolved_parameter["resolved_value"] = resolve_ssm_parameter(
|
|
277
290
|
account_id, region_name, given_value or default_value
|
|
278
291
|
)
|
|
279
|
-
except Exception:
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
292
|
+
except Exception as e:
|
|
293
|
+
# we could not find the parameter however CDK provides the resolved value rather than the
|
|
294
|
+
# parameter name again so try to look up the value in the previous parameters
|
|
295
|
+
if (
|
|
296
|
+
before_parameters
|
|
297
|
+
and (before_param := before_parameters.get(name))
|
|
298
|
+
and isinstance(before_param, dict)
|
|
299
|
+
and (resolved_value := before_param.get("resolved_value"))
|
|
300
|
+
):
|
|
301
|
+
LOG.debug(
|
|
302
|
+
"Parameter %s could not be resolved, using previous value of %s",
|
|
303
|
+
name,
|
|
304
|
+
resolved_value,
|
|
305
|
+
)
|
|
306
|
+
resolved_parameter["resolved_value"] = resolved_value
|
|
307
|
+
else:
|
|
308
|
+
raise ValidationError(
|
|
309
|
+
f"Parameter {name} should either have input value or default value"
|
|
310
|
+
) from e
|
|
283
311
|
elif given_value is None and default_value is None:
|
|
284
312
|
invalid_parameters.append(name)
|
|
285
313
|
continue
|
|
@@ -318,6 +346,7 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
318
346
|
after_parameters,
|
|
319
347
|
change_set.stack.account_id,
|
|
320
348
|
change_set.stack.region_name,
|
|
349
|
+
before_parameters,
|
|
321
350
|
)
|
|
322
351
|
|
|
323
352
|
change_set.resolved_parameters = resolved_parameters
|
|
@@ -435,6 +464,7 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
435
464
|
stack = state.stacks_v2.get(stack_name)
|
|
436
465
|
if not stack:
|
|
437
466
|
raise ValidationError(f"Stack '{stack_name}' does not exist.")
|
|
467
|
+
stack.capabilities = request.get("Capabilities") or []
|
|
438
468
|
else:
|
|
439
469
|
# stack name specified, so fetch the stack by name
|
|
440
470
|
stack_candidates: list[Stack] = [
|
|
@@ -455,6 +485,8 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
455
485
|
if not active_stack_candidates:
|
|
456
486
|
raise ValidationError(f"Stack '{stack_name}' does not exist.")
|
|
457
487
|
stack = active_stack_candidates[0]
|
|
488
|
+
# propagate capabilities from create change set request
|
|
489
|
+
stack.capabilities = request.get("Capabilities") or []
|
|
458
490
|
|
|
459
491
|
# TODO: test if rollback status is allowed as well
|
|
460
492
|
if (
|
|
@@ -465,6 +497,14 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
465
497
|
f"Stack [{stack_name}] already exists and cannot be created again with the changeSet [{change_set_name}]."
|
|
466
498
|
)
|
|
467
499
|
|
|
500
|
+
if change_set_type == ChangeSetType.UPDATE and (
|
|
501
|
+
stack.status == StackStatus.DELETE_COMPLETE
|
|
502
|
+
or stack.status == StackStatus.DELETE_IN_PROGRESS
|
|
503
|
+
):
|
|
504
|
+
raise ValidationError(
|
|
505
|
+
f"Stack:{stack.stack_id} is in {stack.status} state and can not be updated."
|
|
506
|
+
)
|
|
507
|
+
|
|
468
508
|
before_parameters: dict[str, Parameter] | None = None
|
|
469
509
|
match change_set_type:
|
|
470
510
|
case ChangeSetType.UPDATE:
|
|
@@ -525,21 +565,21 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
525
565
|
after_parameters=after_parameters,
|
|
526
566
|
previous_update_model=previous_update_model,
|
|
527
567
|
)
|
|
528
|
-
|
|
529
|
-
# TODO: handle the empty change set case
|
|
530
|
-
if not change_set.has_changes():
|
|
531
|
-
change_set.set_change_set_status(ChangeSetStatus.FAILED)
|
|
568
|
+
if change_set.status == ChangeSetStatus.FAILED:
|
|
532
569
|
change_set.set_execution_status(ExecutionStatus.UNAVAILABLE)
|
|
533
|
-
change_set.status_reason = "The submitted information didn't contain changes. Submit different information to create a change set."
|
|
534
570
|
else:
|
|
535
|
-
if
|
|
536
|
-
|
|
571
|
+
if not change_set.has_changes():
|
|
572
|
+
change_set.set_change_set_status(ChangeSetStatus.FAILED)
|
|
573
|
+
change_set.set_execution_status(ExecutionStatus.UNAVAILABLE)
|
|
574
|
+
change_set.status_reason = "The submitted information didn't contain changes. Submit different information to create a change set."
|
|
575
|
+
else:
|
|
576
|
+
if stack.status not in [StackStatus.CREATE_COMPLETE, StackStatus.UPDATE_COMPLETE]:
|
|
577
|
+
stack.set_stack_status(StackStatus.REVIEW_IN_PROGRESS, "User Initiated")
|
|
537
578
|
|
|
538
|
-
|
|
579
|
+
change_set.set_change_set_status(ChangeSetStatus.CREATE_COMPLETE)
|
|
539
580
|
|
|
540
|
-
stack.change_set_ids.
|
|
581
|
+
stack.change_set_ids.add(change_set.change_set_id)
|
|
541
582
|
state.change_sets[change_set.change_set_id] = change_set
|
|
542
|
-
|
|
543
583
|
return CreateChangeSetOutput(StackId=stack.stack_id, Id=change_set.change_set_id)
|
|
544
584
|
|
|
545
585
|
@handler("ExecuteChangeSet")
|
|
@@ -592,6 +632,11 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
592
632
|
result = change_set_executor.execute()
|
|
593
633
|
change_set.stack.resolved_parameters = change_set.resolved_parameters
|
|
594
634
|
change_set.stack.resolved_resources = result.resources
|
|
635
|
+
change_set.stack.template = change_set.template
|
|
636
|
+
change_set.stack.processed_template = change_set.processed_template
|
|
637
|
+
change_set.stack.template_body = change_set.template_body
|
|
638
|
+
change_set.stack.description = change_set.template.get("Description")
|
|
639
|
+
|
|
595
640
|
if not result.failure_message:
|
|
596
641
|
new_stack_status = StackStatus.UPDATE_COMPLETE
|
|
597
642
|
if change_set.change_set_type == ChangeSetType.CREATE:
|
|
@@ -606,14 +651,6 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
606
651
|
change_set.stack.resolved_exports[export_name] = output["OutputValue"]
|
|
607
652
|
|
|
608
653
|
change_set.stack.change_set_id = change_set.change_set_id
|
|
609
|
-
change_set.stack.change_set_ids.append(change_set.change_set_id)
|
|
610
|
-
|
|
611
|
-
# if the deployment succeeded, update the stack's template representation to that
|
|
612
|
-
# which was just deployed
|
|
613
|
-
change_set.stack.template = change_set.template
|
|
614
|
-
change_set.stack.description = change_set.template.get("Description")
|
|
615
|
-
change_set.stack.processed_template = change_set.processed_template
|
|
616
|
-
change_set.stack.template_body = change_set.template_body
|
|
617
654
|
else:
|
|
618
655
|
LOG.error(
|
|
619
656
|
"Execute change set failed: %s",
|
|
@@ -667,6 +704,27 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
667
704
|
if not change_set:
|
|
668
705
|
raise ChangeSetNotFoundException(f"ChangeSet [{change_set_name}] does not exist")
|
|
669
706
|
|
|
707
|
+
# if the change set failed to create, then we can return a blank response
|
|
708
|
+
if change_set.status == ChangeSetStatus.FAILED:
|
|
709
|
+
return DescribeChangeSetOutput(
|
|
710
|
+
Status=change_set.status,
|
|
711
|
+
ChangeSetId=change_set.change_set_id,
|
|
712
|
+
ChangeSetName=change_set.change_set_name,
|
|
713
|
+
ExecutionStatus=change_set.execution_status,
|
|
714
|
+
RollbackConfiguration=RollbackConfiguration(),
|
|
715
|
+
StackId=change_set.stack.stack_id,
|
|
716
|
+
StackName=change_set.stack.stack_name,
|
|
717
|
+
CreationTime=change_set.creation_time,
|
|
718
|
+
Changes=[],
|
|
719
|
+
Capabilities=change_set.stack.capabilities,
|
|
720
|
+
StatusReason=change_set.status_reason,
|
|
721
|
+
Description=change_set.description,
|
|
722
|
+
# TODO: static information
|
|
723
|
+
IncludeNestedStacks=False,
|
|
724
|
+
NotificationARNs=[],
|
|
725
|
+
Tags=change_set.tags or None,
|
|
726
|
+
)
|
|
727
|
+
|
|
670
728
|
# TODO: The ChangeSetModelDescriber currently matches AWS behavior by listing
|
|
671
729
|
# resource changes in the order they appear in the template. However, when
|
|
672
730
|
# a resource change is triggered indirectly (e.g., via Ref or GetAtt), the
|
|
@@ -701,6 +759,44 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
701
759
|
result["Parameters"] = self._render_resolved_parameters(change_set.resolved_parameters)
|
|
702
760
|
return result
|
|
703
761
|
|
|
762
|
+
@handler("ListChangeSets")
|
|
763
|
+
def list_change_sets(
|
|
764
|
+
self,
|
|
765
|
+
context: RequestContext,
|
|
766
|
+
stack_name: StackNameOrId,
|
|
767
|
+
next_token: NextToken = None,
|
|
768
|
+
**kwargs,
|
|
769
|
+
) -> ListChangeSetsOutput:
|
|
770
|
+
store = get_cloudformation_store(account_id=context.account_id, region_name=context.region)
|
|
771
|
+
stack = find_stack_v2(store, stack_name)
|
|
772
|
+
if not stack:
|
|
773
|
+
raise StackNotFoundError(stack_name)
|
|
774
|
+
summaries = []
|
|
775
|
+
for change_set_id in stack.change_set_ids:
|
|
776
|
+
change_set = store.change_sets[change_set_id]
|
|
777
|
+
if (
|
|
778
|
+
change_set.status != ChangeSetStatus.CREATE_COMPLETE
|
|
779
|
+
or change_set.execution_status != ExecutionStatus.AVAILABLE
|
|
780
|
+
):
|
|
781
|
+
continue
|
|
782
|
+
|
|
783
|
+
summaries.append(
|
|
784
|
+
ChangeSetSummary(
|
|
785
|
+
StackId=change_set.stack.stack_id,
|
|
786
|
+
StackName=change_set.stack.stack_name,
|
|
787
|
+
ChangeSetId=change_set_id,
|
|
788
|
+
ChangeSetName=change_set.change_set_name,
|
|
789
|
+
ExecutionStatus=change_set.execution_status,
|
|
790
|
+
Status=change_set.status,
|
|
791
|
+
StatusReason=change_set.status_reason,
|
|
792
|
+
CreationTime=change_set.creation_time,
|
|
793
|
+
# mocked information
|
|
794
|
+
IncludeNestedStacks=False,
|
|
795
|
+
)
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
return ListChangeSetsOutput(Summaries=summaries)
|
|
799
|
+
|
|
704
800
|
@handler("DeleteChangeSet")
|
|
705
801
|
def delete_change_set(
|
|
706
802
|
self,
|
|
@@ -714,8 +810,22 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
714
810
|
if not change_set:
|
|
715
811
|
return DeleteChangeSetOutput()
|
|
716
812
|
|
|
717
|
-
|
|
718
|
-
|
|
813
|
+
try:
|
|
814
|
+
change_set.stack.change_set_ids.remove(change_set.change_set_id)
|
|
815
|
+
except KeyError:
|
|
816
|
+
LOG.warning(
|
|
817
|
+
"Could not disassociatei change set '%s' from stack '%s', it does not seem to be associated",
|
|
818
|
+
change_set.change_set_id,
|
|
819
|
+
change_set.stack.stack_id,
|
|
820
|
+
)
|
|
821
|
+
try:
|
|
822
|
+
state.change_sets.pop(change_set.change_set_id)
|
|
823
|
+
except KeyError:
|
|
824
|
+
# This _should_ never fail since if we cannot find the change set in the store (using
|
|
825
|
+
# `find_change_set_v2`) then we early return from this function
|
|
826
|
+
LOG.warning(
|
|
827
|
+
"Could not delete change set '%s', it does not exist", change_set.change_set_id
|
|
828
|
+
)
|
|
719
829
|
|
|
720
830
|
return DeleteChangeSetOutput()
|
|
721
831
|
|
|
@@ -999,6 +1109,12 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
999
1109
|
|
|
1000
1110
|
try:
|
|
1001
1111
|
resource = stack.resolved_resources[logical_resource_id]
|
|
1112
|
+
if resource.get("ResourceStatus") not in [
|
|
1113
|
+
StackStatus.CREATE_COMPLETE,
|
|
1114
|
+
StackStatus.UPDATE_COMPLETE,
|
|
1115
|
+
StackStatus.ROLLBACK_COMPLETE,
|
|
1116
|
+
]:
|
|
1117
|
+
raise KeyError
|
|
1002
1118
|
except KeyError:
|
|
1003
1119
|
raise ValidationError(
|
|
1004
1120
|
f"Resource {logical_resource_id} does not exist for stack {stack_name}"
|
|
@@ -1233,8 +1349,8 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
1233
1349
|
def describe_stack_events(
|
|
1234
1350
|
self,
|
|
1235
1351
|
context: RequestContext,
|
|
1236
|
-
stack_name: StackName
|
|
1237
|
-
next_token: NextToken = None,
|
|
1352
|
+
stack_name: StackName,
|
|
1353
|
+
next_token: NextToken | None = None,
|
|
1238
1354
|
**kwargs,
|
|
1239
1355
|
) -> DescribeStackEventsOutput:
|
|
1240
1356
|
if not stack_name:
|
|
@@ -1258,12 +1374,21 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
1258
1374
|
) -> GetTemplateOutput:
|
|
1259
1375
|
state = get_cloudformation_store(context.account_id, context.region)
|
|
1260
1376
|
if change_set_name:
|
|
1377
|
+
if not is_changeset_arn(change_set_name) and not stack_name:
|
|
1378
|
+
raise ValidationError("StackName is a required parameter.")
|
|
1379
|
+
|
|
1261
1380
|
change_set = find_change_set_v2(state, change_set_name, stack_name=stack_name)
|
|
1381
|
+
if not change_set:
|
|
1382
|
+
raise ChangeSetNotFoundException(f"ChangeSet [{change_set_name}] does not exist")
|
|
1262
1383
|
stack = change_set.stack
|
|
1263
1384
|
elif stack_name:
|
|
1264
1385
|
stack = find_stack_v2(state, stack_name)
|
|
1386
|
+
if not stack:
|
|
1387
|
+
raise StackNotFoundError(
|
|
1388
|
+
stack_name, message_override=f"Stack with id {stack_name} does not exist"
|
|
1389
|
+
)
|
|
1265
1390
|
else:
|
|
1266
|
-
raise
|
|
1391
|
+
raise ValidationError("StackName is required if ChangeSetName is not specified.")
|
|
1267
1392
|
|
|
1268
1393
|
if template_stage == TemplateStage.Processed and "Transform" in stack.template_body:
|
|
1269
1394
|
template_body = json.dumps(stack.processed_template)
|
|
@@ -1288,6 +1413,12 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
1288
1413
|
stack = find_stack_v2(state, stack_name)
|
|
1289
1414
|
if not stack:
|
|
1290
1415
|
raise StackNotFoundError(stack_name)
|
|
1416
|
+
|
|
1417
|
+
if stack.status == StackStatus.REVIEW_IN_PROGRESS:
|
|
1418
|
+
raise ValidationError(
|
|
1419
|
+
"GetTemplateSummary cannot be called on REVIEW_IN_PROGRESS stacks."
|
|
1420
|
+
)
|
|
1421
|
+
|
|
1291
1422
|
template = stack.template
|
|
1292
1423
|
else:
|
|
1293
1424
|
template_body = request.get("TemplateBody")
|
|
@@ -1309,6 +1440,11 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
1309
1440
|
template = template_preparer.parse_template(template_body)
|
|
1310
1441
|
|
|
1311
1442
|
id_summaries = defaultdict(list)
|
|
1443
|
+
if "Resources" not in template:
|
|
1444
|
+
raise ValidationError(
|
|
1445
|
+
"Template format error: At least one Resources member must be defined."
|
|
1446
|
+
)
|
|
1447
|
+
|
|
1312
1448
|
for resource_id, resource in template["Resources"].items():
|
|
1313
1449
|
res_type = resource["Type"]
|
|
1314
1450
|
id_summaries[res_type].append(resource_id)
|
|
@@ -1412,6 +1548,14 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
1412
1548
|
raise RuntimeError("Multiple stacks matched, update matching logic")
|
|
1413
1549
|
stack = active_stack_candidates[0]
|
|
1414
1550
|
|
|
1551
|
+
if (
|
|
1552
|
+
stack.status == StackStatus.DELETE_COMPLETE
|
|
1553
|
+
or stack.status == StackStatus.DELETE_IN_PROGRESS
|
|
1554
|
+
):
|
|
1555
|
+
raise ValidationError(
|
|
1556
|
+
f"Stack:{stack.stack_id} is in {stack.status} state and can not be updated."
|
|
1557
|
+
)
|
|
1558
|
+
|
|
1415
1559
|
# TODO: proper status modeling
|
|
1416
1560
|
before_parameters = stack.resolved_parameters
|
|
1417
1561
|
# TODO: reconsider the way parameters are modelled in the update graph process.
|
|
@@ -1559,6 +1703,7 @@ class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
|
1559
1703
|
stack.set_stack_status(StackStatus.DELETE_FAILED)
|
|
1560
1704
|
|
|
1561
1705
|
start_worker_thread(_run)
|
|
1706
|
+
return ExecuteChangeSetOutput()
|
|
1562
1707
|
|
|
1563
1708
|
@handler("ListExports")
|
|
1564
1709
|
def list_exports(
|
|
@@ -17,11 +17,15 @@ class EngineParameter(TypedDict):
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def engine_parameter_value(parameter: EngineParameter) -> str:
|
|
20
|
-
|
|
21
|
-
if
|
|
22
|
-
|
|
20
|
+
given_value = parameter.get("given_value")
|
|
21
|
+
if given_value is not None:
|
|
22
|
+
return given_value
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
default_value = parameter.get("default_value")
|
|
25
|
+
if default_value is not None:
|
|
26
|
+
return default_value
|
|
27
|
+
|
|
28
|
+
raise RuntimeError("Parameter value is None")
|
|
25
29
|
|
|
26
30
|
|
|
27
31
|
class ResolvedResource(TypedDict):
|
|
@@ -15,6 +15,7 @@ from localstack.aws.api.cloudwatch import (
|
|
|
15
15
|
AlarmTypes,
|
|
16
16
|
AmazonResourceName,
|
|
17
17
|
CloudwatchApi,
|
|
18
|
+
ContributorId,
|
|
18
19
|
DashboardBody,
|
|
19
20
|
DashboardName,
|
|
20
21
|
DashboardNamePrefix,
|
|
@@ -107,17 +108,11 @@ _STORE_LOCK = threading.RLock()
|
|
|
107
108
|
AWS_MAX_DATAPOINTS_ACCEPTED: int = 1440
|
|
108
109
|
|
|
109
110
|
|
|
110
|
-
class
|
|
111
|
-
# TODO: check this error against AWS (doesn't exist in the API)
|
|
111
|
+
class ValidationException(CommonServiceException):
|
|
112
112
|
def __init__(self, message: str):
|
|
113
113
|
super().__init__("ValidationError", message, 400, True)
|
|
114
114
|
|
|
115
115
|
|
|
116
|
-
class InvalidParameterCombination(CommonServiceException):
|
|
117
|
-
def __init__(self, message: str):
|
|
118
|
-
super().__init__("InvalidParameterCombination", message, 400, True)
|
|
119
|
-
|
|
120
|
-
|
|
121
116
|
def _validate_parameters_for_put_metric_data(metric_data: MetricData) -> None:
|
|
122
117
|
for index, metric_item in enumerate(metric_data):
|
|
123
118
|
indexplusone = index + 1
|
|
@@ -245,7 +240,7 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook):
|
|
|
245
240
|
results: list[MetricDataResult] = []
|
|
246
241
|
limit = max_datapoints or 100_800
|
|
247
242
|
messages: MetricDataResultMessages = []
|
|
248
|
-
nxt = None
|
|
243
|
+
nxt: str | None = None
|
|
249
244
|
label_additions = []
|
|
250
245
|
|
|
251
246
|
for diff in LABEL_DIFFERENTIATORS:
|
|
@@ -279,14 +274,14 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook):
|
|
|
279
274
|
timestamp_value_dicts = [
|
|
280
275
|
{
|
|
281
276
|
"Timestamp": timestamp,
|
|
282
|
-
"Value": value,
|
|
277
|
+
"Value": float(value),
|
|
283
278
|
}
|
|
284
279
|
for timestamp, value in zip(timestamps, values, strict=False)
|
|
285
280
|
]
|
|
286
281
|
|
|
287
282
|
pagination = PaginatedList(timestamp_value_dicts)
|
|
288
283
|
timestamp_page, nxt = pagination.get_page(
|
|
289
|
-
lambda item: item.get("Timestamp"),
|
|
284
|
+
lambda item: str(item.get("Timestamp")),
|
|
290
285
|
next_token=next_token,
|
|
291
286
|
page_size=limit,
|
|
292
287
|
)
|
|
@@ -314,6 +309,11 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook):
|
|
|
314
309
|
state_reason_data: StateReasonData = None,
|
|
315
310
|
**kwargs,
|
|
316
311
|
) -> None:
|
|
312
|
+
if state_value not in ("OK", "ALARM", "INSUFFICIENT_DATA"):
|
|
313
|
+
raise ValidationException(
|
|
314
|
+
f"1 validation error detected: Value '{state_value}' at 'stateValue' failed to satisfy constraint: Member must satisfy enum value set: [INSUFFICIENT_DATA, ALARM, OK]"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
317
|
try:
|
|
318
318
|
if state_reason_data:
|
|
319
319
|
state_reason_data = json.loads(state_reason_data)
|
|
@@ -332,10 +332,6 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook):
|
|
|
332
332
|
raise ResourceNotFound()
|
|
333
333
|
|
|
334
334
|
old_state = alarm.alarm["StateValue"]
|
|
335
|
-
if state_value not in ("OK", "ALARM", "INSUFFICIENT_DATA"):
|
|
336
|
-
raise ValidationError(
|
|
337
|
-
f"1 validation error detected: Value '{state_value}' at 'stateValue' failed to satisfy constraint: Member must satisfy enum value set: [INSUFFICIENT_DATA, ALARM, OK]"
|
|
338
|
-
)
|
|
339
335
|
|
|
340
336
|
old_state_reason = alarm.alarm["StateReason"]
|
|
341
337
|
old_state_update_timestamp = alarm.alarm["StateUpdatedTimestamp"]
|
|
@@ -415,7 +411,7 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook):
|
|
|
415
411
|
"ignore",
|
|
416
412
|
"missing",
|
|
417
413
|
]:
|
|
418
|
-
raise
|
|
414
|
+
raise ValidationException(
|
|
419
415
|
f"The value {request['TreatMissingData']} is not supported for TreatMissingData parameter. Supported values are [breaching, notBreaching, ignore, missing]."
|
|
420
416
|
)
|
|
421
417
|
# do some sanity checks:
|
|
@@ -424,7 +420,7 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook):
|
|
|
424
420
|
value = request.get("Period")
|
|
425
421
|
if value not in (10, 30):
|
|
426
422
|
if value % 60 != 0:
|
|
427
|
-
raise
|
|
423
|
+
raise ValidationException("Period must be 10, 30 or a multiple of 60")
|
|
428
424
|
if request.get("Statistic"):
|
|
429
425
|
if request.get("Statistic") not in [
|
|
430
426
|
"SampleCount",
|
|
@@ -433,7 +429,7 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook):
|
|
|
433
429
|
"Minimum",
|
|
434
430
|
"Maximum",
|
|
435
431
|
]:
|
|
436
|
-
raise
|
|
432
|
+
raise ValidationException(
|
|
437
433
|
f"Value '{request.get('Statistic')}' at 'statistic' failed to satisfy constraint: Member must satisfy enum value set: [Maximum, SampleCount, Sum, Minimum, Average]"
|
|
438
434
|
)
|
|
439
435
|
|
|
@@ -447,7 +443,7 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook):
|
|
|
447
443
|
"evaluate",
|
|
448
444
|
"ignore",
|
|
449
445
|
):
|
|
450
|
-
raise
|
|
446
|
+
raise ValidationException(
|
|
451
447
|
f"Option {evaluate_low_sample_count_percentile} is not supported. "
|
|
452
448
|
"Supported options for parameter EvaluateLowSampleCountPercentile are evaluate and ignore."
|
|
453
449
|
)
|
|
@@ -690,7 +686,7 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook):
|
|
|
690
686
|
expected_datapoints = (end_time_unix - start_time_unix) / period
|
|
691
687
|
|
|
692
688
|
if expected_datapoints > AWS_MAX_DATAPOINTS_ACCEPTED:
|
|
693
|
-
raise
|
|
689
|
+
raise InvalidParameterCombinationException(
|
|
694
690
|
f"You have requested up to {int(expected_datapoints)} datapoints, which exceeds the limit of {AWS_MAX_DATAPOINTS_ACCEPTED}. "
|
|
695
691
|
f"You may reduce the datapoints requested by increasing Period, or decreasing the time range."
|
|
696
692
|
)
|
|
@@ -737,7 +733,7 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook):
|
|
|
737
733
|
for i, timestamp in enumerate(timestamps):
|
|
738
734
|
stat_datapoints.setdefault(selected_unit, {})
|
|
739
735
|
stat_datapoints[selected_unit].setdefault(timestamp, {})
|
|
740
|
-
stat_datapoints[selected_unit][timestamp][stat] = values[i]
|
|
736
|
+
stat_datapoints[selected_unit][timestamp][stat] = float(values[i])
|
|
741
737
|
stat_datapoints[selected_unit][timestamp]["Unit"] = selected_unit
|
|
742
738
|
|
|
743
739
|
datapoints: list[Datapoint] = []
|
|
@@ -822,14 +818,15 @@ class CloudwatchProvider(CloudwatchApi, ServiceLifecycleHook):
|
|
|
822
818
|
def describe_alarm_history(
|
|
823
819
|
self,
|
|
824
820
|
context: RequestContext,
|
|
825
|
-
alarm_name: AlarmName = None,
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
821
|
+
alarm_name: AlarmName | None = None,
|
|
822
|
+
alarm_contributor_id: ContributorId | None = None,
|
|
823
|
+
alarm_types: AlarmTypes | None = None,
|
|
824
|
+
history_item_type: HistoryItemType | None = None,
|
|
825
|
+
start_date: Timestamp | None = None,
|
|
826
|
+
end_date: Timestamp | None = None,
|
|
827
|
+
max_records: MaxRecords | None = None,
|
|
828
|
+
next_token: NextToken | None = None,
|
|
829
|
+
scan_by: ScanBy | None = None,
|
|
833
830
|
**kwargs,
|
|
834
831
|
) -> DescribeAlarmHistoryOutput:
|
|
835
832
|
store = self.get_store(context.account_id, context.region)
|
|
@@ -17,7 +17,8 @@ from localstack.utils.run import run
|
|
|
17
17
|
DDB_AGENT_JAR_URL = f"{ARTIFACTS_REPO}/raw/e4e8c8e294b1fcda90c678ff6af5d5ebe1f091eb/dynamodb-local-patch/target/ddb-local-loader-0.2.jar"
|
|
18
18
|
JAVASSIST_JAR_URL = f"{MAVEN_REPO_URL}/org/javassist/javassist/3.30.2-GA/javassist-3.30.2-GA.jar"
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
# URL points to 2.x here - however the latest 3.x builds are available under this URL
|
|
21
|
+
DDBLOCAL_URL = "https://d1ni2b6xgvw0s0.cloudfront.net/v2.x/dynamodb_local_latest.zip"
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class DynamoDBLocalPackage(Package):
|