localstack-core 4.6.1.dev74__py3-none-any.whl → 4.10.1.dev7__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/apigateway/__init__.py +87 -110
- localstack/aws/api/cloudformation/__init__.py +18 -4
- localstack/aws/api/cloudwatch/__init__.py +41 -1
- localstack/aws/api/config/__init__.py +4 -0
- localstack/aws/api/core.py +8 -5
- localstack/aws/api/dynamodb/__init__.py +30 -0
- localstack/aws/api/ec2/__init__.py +1524 -64
- localstack/aws/api/events/__init__.py +12 -16
- localstack/aws/api/iam/__init__.py +7 -0
- localstack/aws/api/kinesis/__init__.py +19 -0
- localstack/aws/api/kms/__init__.py +6 -0
- localstack/aws/api/lambda_/__init__.py +13 -0
- localstack/aws/api/logs/__init__.py +20 -8
- localstack/aws/api/opensearch/__init__.py +16 -0
- localstack/aws/api/pipes/__init__.py +24 -32
- localstack/aws/api/redshift/__init__.py +9 -3
- localstack/aws/api/route53/__init__.py +5 -0
- localstack/aws/api/s3/__init__.py +12 -0
- localstack/aws/api/s3control/__init__.py +56 -0
- localstack/aws/api/scheduler/__init__.py +14 -16
- localstack/aws/api/ssm/__init__.py +2 -0
- localstack/aws/api/stepfunctions/__init__.py +88 -114
- localstack/aws/api/support/__init__.py +8 -9
- localstack/aws/api/transcribe/__init__.py +17 -0
- localstack/aws/chain.py +2 -2
- localstack/aws/client.py +14 -11
- localstack/aws/connect.py +42 -41
- localstack/aws/forwarder.py +57 -9
- localstack/aws/gateway.py +1 -3
- localstack/aws/handlers/analytics.py +2 -3
- localstack/aws/handlers/cors.py +4 -5
- localstack/aws/handlers/internal_requests.py +6 -1
- localstack/aws/handlers/logging.py +13 -4
- localstack/aws/handlers/metric_handler.py +44 -5
- localstack/aws/handlers/service.py +48 -28
- localstack/aws/mocking.py +18 -27
- localstack/aws/patches.py +2 -2
- localstack/aws/protocol/op_router.py +11 -10
- localstack/aws/protocol/parser.py +475 -49
- localstack/aws/protocol/serializer.py +723 -106
- localstack/aws/protocol/service_router.py +133 -33
- localstack/aws/protocol/validate.py +6 -6
- localstack/aws/scaffold.py +9 -10
- localstack/aws/serving/edge.py +5 -6
- localstack/aws/serving/hypercorn.py +2 -2
- localstack/aws/serving/twisted.py +1 -2
- localstack/aws/serving/werkzeug.py +2 -2
- localstack/aws/skeleton.py +12 -11
- localstack/aws/spec-patches.json +58 -0
- localstack/aws/spec.py +66 -46
- localstack/cli/exceptions.py +1 -1
- localstack/cli/localstack.py +11 -11
- localstack/cli/lpm.py +4 -5
- localstack/cli/plugins.py +1 -1
- localstack/cli/profiles.py +1 -2
- localstack/config.py +40 -31
- localstack/constants.py +4 -29
- localstack/deprecations.py +5 -5
- localstack/dev/kubernetes/__main__.py +85 -5
- localstack/dev/run/__main__.py +5 -5
- localstack/dev/run/configurators.py +1 -4
- localstack/dev/run/paths.py +6 -6
- localstack/dns/models.py +2 -1
- localstack/dns/plugins.py +5 -1
- localstack/dns/server.py +16 -6
- localstack/http/dispatcher.py +1 -2
- localstack/http/response.py +2 -2
- localstack/http/router.py +1 -1
- localstack/http/trace.py +2 -1
- localstack/logging/format.py +6 -6
- localstack/packages/api.py +13 -12
- localstack/packages/core.py +4 -4
- localstack/packages/debugpy.py +1 -3
- localstack/packages/ffmpeg.py +1 -2
- localstack/packages/java.py +38 -12
- localstack/packages/plugins.py +0 -8
- localstack/runtime/analytics.py +3 -0
- localstack/runtime/hooks.py +1 -1
- localstack/runtime/init.py +8 -9
- localstack/runtime/main.py +5 -5
- localstack/runtime/patches.py +2 -2
- localstack/runtime/shutdown.py +2 -1
- localstack/services/apigateway/exporter.py +1 -2
- localstack/services/apigateway/helpers.py +7 -10
- localstack/services/apigateway/legacy/context.py +21 -21
- localstack/services/apigateway/legacy/helpers.py +27 -28
- localstack/services/apigateway/legacy/integration.py +11 -10
- localstack/services/apigateway/legacy/invocations.py +6 -5
- localstack/services/apigateway/legacy/provider.py +148 -68
- localstack/services/apigateway/legacy/router_asf.py +2 -2
- localstack/services/apigateway/legacy/templates.py +6 -6
- localstack/services/apigateway/models.py +16 -16
- localstack/services/apigateway/next_gen/execute_api/api.py +2 -2
- localstack/services/apigateway/next_gen/execute_api/context.py +25 -25
- localstack/services/apigateway/next_gen/execute_api/handlers/api_key_validation.py +2 -5
- localstack/services/apigateway/next_gen/execute_api/handlers/method_request.py +7 -2
- localstack/services/apigateway/next_gen/execute_api/handlers/parse.py +1 -2
- localstack/services/apigateway/next_gen/execute_api/handlers/resource_router.py +2 -3
- localstack/services/apigateway/next_gen/execute_api/header_utils.py +1 -1
- localstack/services/apigateway/next_gen/execute_api/helpers.py +2 -2
- localstack/services/apigateway/next_gen/execute_api/integrations/aws.py +9 -6
- localstack/services/apigateway/next_gen/execute_api/integrations/http.py +12 -12
- localstack/services/apigateway/next_gen/execute_api/router.py +31 -0
- localstack/services/apigateway/next_gen/execute_api/template_mapping.py +2 -2
- localstack/services/apigateway/next_gen/execute_api/test_invoke.py +114 -9
- localstack/services/apigateway/next_gen/execute_api/variables.py +63 -63
- localstack/services/apigateway/next_gen/provider.py +5 -0
- localstack/services/apigateway/resource_providers/aws_apigateway_account.py +3 -3
- localstack/services/apigateway/resource_providers/aws_apigateway_account_plugin.py +1 -3
- localstack/services/apigateway/resource_providers/aws_apigateway_apikey.py +14 -14
- localstack/services/apigateway/resource_providers/aws_apigateway_apikey_plugin.py +1 -3
- localstack/services/apigateway/resource_providers/aws_apigateway_basepathmapping.py +5 -5
- localstack/services/apigateway/resource_providers/aws_apigateway_basepathmapping_plugin.py +1 -3
- localstack/services/apigateway/resource_providers/aws_apigateway_deployment.py +46 -46
- localstack/services/apigateway/resource_providers/aws_apigateway_deployment_plugin.py +1 -3
- localstack/services/apigateway/resource_providers/aws_apigateway_domainname.py +18 -18
- localstack/services/apigateway/resource_providers/aws_apigateway_domainname_plugin.py +1 -3
- localstack/services/apigateway/resource_providers/aws_apigateway_gatewayresponse.py +7 -7
- localstack/services/apigateway/resource_providers/aws_apigateway_gatewayresponse_plugin.py +1 -3
- localstack/services/apigateway/resource_providers/aws_apigateway_method.py +36 -36
- localstack/services/apigateway/resource_providers/aws_apigateway_method_plugin.py +1 -3
- localstack/services/apigateway/resource_providers/aws_apigateway_model.py +6 -6
- localstack/services/apigateway/resource_providers/aws_apigateway_model_plugin.py +1 -3
- localstack/services/apigateway/resource_providers/aws_apigateway_requestvalidator.py +6 -6
- localstack/services/apigateway/resource_providers/aws_apigateway_requestvalidator_plugin.py +1 -3
- localstack/services/apigateway/resource_providers/aws_apigateway_resource.py +6 -6
- localstack/services/apigateway/resource_providers/aws_apigateway_resource_plugin.py +1 -3
- localstack/services/apigateway/resource_providers/aws_apigateway_restapi.py +26 -26
- localstack/services/apigateway/resource_providers/aws_apigateway_restapi_plugin.py +1 -3
- localstack/services/apigateway/resource_providers/aws_apigateway_stage.py +33 -33
- localstack/services/apigateway/resource_providers/aws_apigateway_stage_plugin.py +1 -3
- localstack/services/apigateway/resource_providers/aws_apigateway_usageplan.py +18 -18
- localstack/services/apigateway/resource_providers/aws_apigateway_usageplan_plugin.py +1 -3
- localstack/services/apigateway/resource_providers/aws_apigateway_usageplankey.py +5 -5
- localstack/services/apigateway/resource_providers/aws_apigateway_usageplankey_plugin.py +1 -3
- localstack/services/cdk/resource_providers/cdk_metadata.py +4 -3
- localstack/services/cdk/resource_providers/cdk_metadata_plugin.py +1 -3
- localstack/services/certificatemanager/resource_providers/aws_certificatemanager_certificate.py +14 -14
- localstack/services/certificatemanager/resource_providers/aws_certificatemanager_certificate_plugin.py +1 -3
- localstack/services/cloudformation/api_utils.py +4 -8
- localstack/services/cloudformation/cfn_utils.py +2 -2
- localstack/services/cloudformation/deployment_utils.py +14 -12
- localstack/services/cloudformation/engine/changes.py +3 -3
- localstack/services/cloudformation/engine/entities.py +27 -17
- localstack/services/cloudformation/engine/parameters.py +4 -4
- localstack/services/cloudformation/engine/template_deployer.py +15 -14
- localstack/services/cloudformation/engine/template_utils.py +34 -12
- localstack/services/cloudformation/engine/transformers.py +11 -8
- localstack/services/cloudformation/engine/types.py +5 -4
- localstack/services/cloudformation/engine/v2/change_set_model.py +336 -39
- localstack/services/cloudformation/engine/v2/change_set_model_describer.py +96 -17
- localstack/services/cloudformation/engine/v2/change_set_model_executor.py +286 -139
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +315 -146
- localstack/services/cloudformation/engine/v2/change_set_model_transform.py +380 -105
- localstack/services/cloudformation/engine/v2/change_set_model_validator.py +161 -31
- localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +6 -0
- localstack/services/cloudformation/engine/v2/resolving.py +102 -0
- localstack/services/cloudformation/engine/yaml_parser.py +9 -2
- localstack/services/cloudformation/provider.py +8 -6
- localstack/services/cloudformation/provider_utils.py +34 -4
- localstack/services/cloudformation/resource_provider.py +31 -21
- localstack/services/cloudformation/resource_providers/aws_cloudformation_macro.py +7 -7
- localstack/services/cloudformation/resource_providers/aws_cloudformation_macro_plugin.py +1 -3
- localstack/services/cloudformation/resource_providers/aws_cloudformation_stack.py +9 -9
- localstack/services/cloudformation/resource_providers/aws_cloudformation_stack_plugin.py +1 -3
- localstack/services/cloudformation/resource_providers/aws_cloudformation_waitcondition.py +6 -6
- localstack/services/cloudformation/resource_providers/aws_cloudformation_waitcondition_plugin.py +1 -3
- localstack/services/cloudformation/resource_providers/aws_cloudformation_waitconditionhandle.py +2 -2
- localstack/services/cloudformation/resource_providers/aws_cloudformation_waitconditionhandle_plugin.py +1 -3
- localstack/services/cloudformation/resources.py +24149 -0
- localstack/services/cloudformation/scaffolding/__main__.py +13 -12
- localstack/services/cloudformation/scaffolding/propgen.py +2 -2
- localstack/services/cloudformation/service_models.py +2 -2
- localstack/services/cloudformation/stores.py +31 -22
- localstack/services/cloudformation/v2/entities.py +65 -92
- localstack/services/cloudformation/v2/provider.py +553 -128
- localstack/services/cloudformation/v2/types.py +38 -0
- localstack/services/cloudformation/v2/utils.py +4 -1
- localstack/services/cloudwatch/alarm_scheduler.py +11 -8
- localstack/services/cloudwatch/cloudwatch_database_helper.py +4 -5
- localstack/services/cloudwatch/models.py +5 -7
- localstack/services/cloudwatch/provider.py +30 -25
- localstack/services/cloudwatch/provider_v2.py +27 -32
- localstack/services/cloudwatch/resource_providers/aws_cloudwatch_alarm.py +40 -40
- localstack/services/cloudwatch/resource_providers/aws_cloudwatch_alarm_plugin.py +1 -3
- localstack/services/cloudwatch/resource_providers/aws_cloudwatch_compositealarm.py +12 -12
- localstack/services/cloudwatch/resource_providers/aws_cloudwatch_compositealarm_plugin.py +1 -3
- localstack/services/dynamodb/packages.py +3 -3
- localstack/services/dynamodb/provider.py +49 -8
- localstack/services/dynamodb/resource_providers/aws_dynamodb_globaltable.py +63 -63
- localstack/services/dynamodb/resource_providers/aws_dynamodb_globaltable_plugin.py +1 -3
- localstack/services/dynamodb/resource_providers/aws_dynamodb_table.py +57 -57
- localstack/services/dynamodb/resource_providers/aws_dynamodb_table_plugin.py +1 -3
- localstack/services/dynamodb/server.py +2 -2
- localstack/services/dynamodb/utils.py +13 -14
- localstack/services/dynamodb/v2/provider.py +48 -7
- localstack/services/dynamodbstreams/dynamodbstreams_api.py +2 -2
- localstack/services/dynamodbstreams/models.py +1 -3
- localstack/services/ec2/patches.py +26 -5
- localstack/services/ec2/provider.py +3 -3
- localstack/services/ec2/resource_providers/aws_ec2_dhcpoptions.py +10 -10
- localstack/services/ec2/resource_providers/aws_ec2_dhcpoptions_plugin.py +1 -3
- localstack/services/ec2/resource_providers/aws_ec2_instance.py +96 -96
- localstack/services/ec2/resource_providers/aws_ec2_instance_plugin.py +1 -3
- localstack/services/ec2/resource_providers/aws_ec2_internetgateway.py +5 -5
- localstack/services/ec2/resource_providers/aws_ec2_internetgateway_plugin.py +1 -3
- localstack/services/ec2/resource_providers/aws_ec2_keypair.py +10 -10
- localstack/services/ec2/resource_providers/aws_ec2_keypair_plugin.py +1 -3
- localstack/services/ec2/resource_providers/aws_ec2_natgateway.py +13 -13
- localstack/services/ec2/resource_providers/aws_ec2_natgateway_plugin.py +1 -3
- localstack/services/ec2/resource_providers/aws_ec2_networkacl.py +6 -6
- localstack/services/ec2/resource_providers/aws_ec2_networkacl_plugin.py +1 -3
- localstack/services/ec2/resource_providers/aws_ec2_prefixlist.py +14 -14
- localstack/services/ec2/resource_providers/aws_ec2_prefixlist_plugin.py +1 -3
- localstack/services/ec2/resource_providers/aws_ec2_route.py +15 -15
- localstack/services/ec2/resource_providers/aws_ec2_route_plugin.py +1 -3
- localstack/services/ec2/resource_providers/aws_ec2_routetable.py +6 -6
- localstack/services/ec2/resource_providers/aws_ec2_routetable_plugin.py +1 -3
- localstack/services/ec2/resource_providers/aws_ec2_securitygroup.py +29 -29
- localstack/services/ec2/resource_providers/aws_ec2_securitygroup_plugin.py +1 -3
- localstack/services/ec2/resource_providers/aws_ec2_subnet.py +19 -19
- localstack/services/ec2/resource_providers/aws_ec2_subnet_plugin.py +1 -3
- localstack/services/ec2/resource_providers/aws_ec2_subnetroutetableassociation.py +4 -4
- localstack/services/ec2/resource_providers/aws_ec2_subnetroutetableassociation_plugin.py +1 -3
- localstack/services/ec2/resource_providers/aws_ec2_transitgateway.py +16 -16
- localstack/services/ec2/resource_providers/aws_ec2_transitgateway_plugin.py +1 -3
- localstack/services/ec2/resource_providers/aws_ec2_transitgatewayattachment.py +9 -9
- localstack/services/ec2/resource_providers/aws_ec2_transitgatewayattachment_plugin.py +1 -3
- localstack/services/ec2/resource_providers/aws_ec2_vpc.py +15 -15
- localstack/services/ec2/resource_providers/aws_ec2_vpc_plugin.py +1 -3
- localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint.py +29 -22
- localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint_plugin.py +1 -3
- localstack/services/ec2/resource_providers/aws_ec2_vpcgatewayattachment.py +5 -5
- localstack/services/ec2/resource_providers/aws_ec2_vpcgatewayattachment_plugin.py +1 -3
- localstack/services/ecr/resource_providers/aws_ecr_repository.py +22 -19
- localstack/services/ecr/resource_providers/aws_ecr_repository_plugin.py +1 -3
- localstack/services/edge.py +6 -6
- localstack/services/es/provider.py +21 -21
- localstack/services/events/archive.py +2 -2
- localstack/services/events/connection.py +5 -5
- localstack/services/events/event_bus.py +9 -9
- localstack/services/events/event_rule_engine.py +31 -13
- localstack/services/events/models.py +29 -30
- localstack/services/events/provider.py +29 -26
- localstack/services/events/replay.py +3 -3
- localstack/services/events/resource_providers/aws_events_apidestination.py +8 -8
- localstack/services/events/resource_providers/aws_events_apidestination_plugin.py +1 -3
- localstack/services/events/resource_providers/aws_events_connection.py +27 -27
- localstack/services/events/resource_providers/aws_events_connection_plugin.py +1 -3
- localstack/services/events/resource_providers/aws_events_eventbus.py +9 -9
- localstack/services/events/resource_providers/aws_events_eventbus_plugin.py +1 -3
- localstack/services/events/resource_providers/aws_events_eventbuspolicy.py +11 -11
- localstack/services/events/resource_providers/aws_events_eventbuspolicy_plugin.py +1 -3
- localstack/services/events/resource_providers/aws_events_rule.py +82 -82
- localstack/services/events/resource_providers/aws_events_rule_plugin.py +1 -3
- localstack/services/events/rule.py +15 -15
- localstack/services/events/target.py +21 -13
- localstack/services/events/utils.py +7 -7
- localstack/services/events/v1/models.py +1 -3
- localstack/services/events/v1/provider.py +15 -15
- localstack/services/firehose/models.py +1 -3
- localstack/services/firehose/provider.py +25 -16
- localstack/services/iam/iam_patches.py +5 -6
- localstack/services/iam/provider.py +14 -119
- localstack/services/iam/resource_providers/aws_iam_accesskey.py +6 -6
- localstack/services/iam/resource_providers/aws_iam_accesskey_plugin.py +1 -3
- localstack/services/iam/resource_providers/aws_iam_group.py +9 -9
- localstack/services/iam/resource_providers/aws_iam_group_plugin.py +1 -3
- localstack/services/iam/resource_providers/aws_iam_instanceprofile.py +5 -5
- localstack/services/iam/resource_providers/aws_iam_instanceprofile_plugin.py +1 -3
- localstack/services/iam/resource_providers/aws_iam_managedpolicy.py +9 -9
- localstack/services/iam/resource_providers/aws_iam_managedpolicy_plugin.py +1 -3
- localstack/services/iam/resource_providers/aws_iam_policy.py +7 -7
- localstack/services/iam/resource_providers/aws_iam_policy_plugin.py +1 -3
- localstack/services/iam/resource_providers/aws_iam_role.py +16 -16
- localstack/services/iam/resource_providers/aws_iam_role_plugin.py +1 -3
- localstack/services/iam/resource_providers/aws_iam_servercertificate.py +10 -10
- localstack/services/iam/resource_providers/aws_iam_servercertificate_plugin.py +1 -3
- localstack/services/iam/resource_providers/aws_iam_servicelinkedrole.py +5 -5
- localstack/services/iam/resource_providers/aws_iam_servicelinkedrole_plugin.py +1 -3
- localstack/services/iam/resource_providers/aws_iam_user.py +17 -17
- localstack/services/iam/resource_providers/aws_iam_user_plugin.py +1 -3
- localstack/services/iam/resources/policy_simulator.py +133 -0
- localstack/services/kinesis/kinesis_mock_server.py +7 -8
- localstack/services/kinesis/models.py +17 -5
- localstack/services/kinesis/packages.py +3 -3
- localstack/services/kinesis/provider.py +86 -3
- localstack/services/kinesis/resource_providers/aws_kinesis_stream.py +13 -13
- localstack/services/kinesis/resource_providers/aws_kinesis_stream_plugin.py +1 -3
- localstack/services/kinesis/resource_providers/aws_kinesis_streamconsumer.py +7 -7
- localstack/services/kinesis/resource_providers/aws_kinesis_streamconsumer_plugin.py +1 -3
- localstack/services/kinesisfirehose/resource_providers/aws_kinesisfirehose_deliverystream.py +191 -191
- localstack/services/kinesisfirehose/resource_providers/aws_kinesisfirehose_deliverystream_plugin.py +1 -3
- localstack/services/kms/models.py +21 -18
- localstack/services/kms/provider.py +17 -11
- localstack/services/kms/resource_providers/aws_kms_alias.py +3 -3
- localstack/services/kms/resource_providers/aws_kms_alias_plugin.py +1 -3
- localstack/services/kms/resource_providers/aws_kms_key.py +14 -14
- localstack/services/kms/resource_providers/aws_kms_key_plugin.py +1 -3
- localstack/services/kms/utils.py +4 -3
- localstack/services/lambda_/api_utils.py +19 -16
- localstack/services/lambda_/custom_endpoints.py +2 -2
- localstack/services/lambda_/event_source_mapping/esm_worker_factory.py +4 -7
- localstack/services/lambda_/event_source_mapping/pipe_utils.py +2 -2
- localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +1 -1
- localstack/services/lambda_/event_source_mapping/senders/sender_utils.py +2 -1
- localstack/services/lambda_/hooks.py +6 -1
- localstack/services/lambda_/invocation/assignment.py +1 -2
- localstack/services/lambda_/invocation/docker_runtime_executor.py +7 -11
- localstack/services/lambda_/invocation/event_manager.py +1 -1
- localstack/services/lambda_/invocation/execution_environment.py +4 -4
- localstack/services/lambda_/invocation/executor_endpoint.py +8 -11
- localstack/services/lambda_/invocation/internal_sqs_queue.py +6 -10
- localstack/services/lambda_/invocation/lambda_models.py +33 -30
- localstack/services/lambda_/invocation/lambda_service.py +12 -5
- localstack/services/lambda_/invocation/logs.py +2 -3
- localstack/services/lambda_/invocation/runtime_executor.py +3 -3
- localstack/services/lambda_/invocation/version_manager.py +31 -8
- localstack/services/lambda_/ldm.py +14 -0
- localstack/services/lambda_/packages.py +3 -4
- localstack/services/lambda_/provider.py +9 -26
- localstack/services/lambda_/provider_utils.py +1 -1
- localstack/services/lambda_/resource_providers/aws_lambda_codesigningconfig.py +8 -8
- localstack/services/lambda_/resource_providers/aws_lambda_codesigningconfig_plugin.py +1 -3
- localstack/services/lambda_/resource_providers/aws_lambda_eventinvokeconfig.py +11 -11
- localstack/services/lambda_/resource_providers/aws_lambda_eventinvokeconfig_plugin.py +1 -3
- localstack/services/lambda_/resource_providers/aws_lambda_eventsourcemapping.py +39 -39
- localstack/services/lambda_/resource_providers/aws_lambda_eventsourcemapping_plugin.py +1 -3
- localstack/services/lambda_/resource_providers/aws_lambda_function.py +54 -54
- localstack/services/lambda_/resource_providers/aws_lambda_function_plugin.py +1 -3
- localstack/services/lambda_/resource_providers/aws_lambda_layerversion.py +11 -11
- localstack/services/lambda_/resource_providers/aws_lambda_layerversion_plugin.py +1 -3
- localstack/services/lambda_/resource_providers/aws_lambda_layerversionpermission.py +6 -6
- localstack/services/lambda_/resource_providers/aws_lambda_layerversionpermission_plugin.py +1 -3
- localstack/services/lambda_/resource_providers/aws_lambda_permission.py +10 -10
- localstack/services/lambda_/resource_providers/aws_lambda_permission_plugin.py +1 -3
- localstack/services/lambda_/resource_providers/aws_lambda_url.py +15 -15
- localstack/services/lambda_/resource_providers/aws_lambda_url_plugin.py +1 -3
- localstack/services/lambda_/resource_providers/aws_lambda_version.py +8 -8
- localstack/services/lambda_/resource_providers/aws_lambda_version_plugin.py +1 -3
- localstack/services/lambda_/resource_providers/lambda_alias.py +12 -12
- localstack/services/lambda_/resource_providers/lambda_alias_plugin.py +1 -3
- localstack/services/lambda_/runtimes.py +1 -3
- localstack/services/lambda_/urlrouter.py +14 -1
- localstack/services/logs/models.py +1 -3
- localstack/services/logs/provider.py +4 -4
- localstack/services/logs/resource_providers/aws_logs_loggroup.py +9 -9
- localstack/services/logs/resource_providers/aws_logs_loggroup_plugin.py +1 -3
- localstack/services/logs/resource_providers/aws_logs_logstream.py +4 -4
- localstack/services/logs/resource_providers/aws_logs_logstream_plugin.py +1 -3
- localstack/services/logs/resource_providers/aws_logs_subscriptionfilter.py +7 -7
- localstack/services/logs/resource_providers/aws_logs_subscriptionfilter_plugin.py +1 -3
- localstack/services/moto.py +5 -4
- localstack/services/opensearch/cluster.py +28 -20
- localstack/services/opensearch/cluster_manager.py +13 -14
- localstack/services/opensearch/models.py +1 -3
- localstack/services/opensearch/packages.py +27 -9
- localstack/services/opensearch/provider.py +15 -10
- localstack/services/opensearch/resource_providers/aws_elasticsearch_domain.py +61 -61
- localstack/services/opensearch/resource_providers/aws_elasticsearch_domain_plugin.py +1 -3
- localstack/services/opensearch/resource_providers/aws_opensearchservice_domain.py +89 -89
- localstack/services/opensearch/resource_providers/aws_opensearchservice_domain_plugin.py +1 -3
- localstack/services/opensearch/versions.py +57 -10
- localstack/services/plugins.py +37 -32
- localstack/services/providers.py +10 -2
- localstack/services/redshift/provider.py +0 -21
- localstack/services/redshift/resource_providers/aws_redshift_cluster.py +57 -57
- localstack/services/redshift/resource_providers/aws_redshift_cluster_plugin.py +1 -3
- localstack/services/resource_groups/resource_providers/aws_resourcegroups_group.py +21 -21
- localstack/services/resource_groups/resource_providers/aws_resourcegroups_group_plugin.py +1 -3
- localstack/services/route53/models.py +1 -3
- localstack/services/route53/provider.py +1 -2
- localstack/services/route53/resource_providers/aws_route53_healthcheck.py +6 -6
- localstack/services/route53/resource_providers/aws_route53_healthcheck_plugin.py +1 -3
- localstack/services/route53/resource_providers/aws_route53_recordset.py +27 -27
- localstack/services/route53/resource_providers/aws_route53_recordset_plugin.py +1 -3
- localstack/services/route53resolver/models.py +8 -10
- localstack/services/route53resolver/provider.py +12 -12
- localstack/services/s3/codec.py +2 -2
- localstack/services/s3/constants.py +5 -2
- localstack/services/s3/cors.py +8 -8
- localstack/services/s3/models.py +73 -73
- localstack/services/s3/notifications.py +74 -58
- localstack/services/s3/presigned_url.py +41 -59
- localstack/services/s3/provider.py +86 -29
- localstack/services/s3/resource_providers/aws_s3_bucket.py +180 -180
- localstack/services/s3/resource_providers/aws_s3_bucket_plugin.py +1 -3
- localstack/services/s3/resource_providers/aws_s3_bucketpolicy.py +4 -4
- localstack/services/s3/resource_providers/aws_s3_bucketpolicy_plugin.py +1 -3
- localstack/services/s3/storage/core.py +4 -3
- localstack/services/s3/storage/ephemeral.py +7 -6
- localstack/services/s3/utils.py +51 -31
- localstack/services/s3/validation.py +47 -33
- localstack/services/s3/website_hosting.py +9 -7
- localstack/services/scheduler/resource_providers/aws_scheduler_schedule.py +60 -60
- localstack/services/scheduler/resource_providers/aws_scheduler_schedule_plugin.py +1 -3
- localstack/services/scheduler/resource_providers/aws_scheduler_schedulegroup.py +9 -9
- localstack/services/scheduler/resource_providers/aws_scheduler_schedulegroup_plugin.py +1 -3
- localstack/services/secretsmanager/provider.py +10 -12
- localstack/services/secretsmanager/resource_providers/aws_secretsmanager_resourcepolicy.py +5 -5
- localstack/services/secretsmanager/resource_providers/aws_secretsmanager_resourcepolicy_plugin.py +1 -3
- localstack/services/secretsmanager/resource_providers/aws_secretsmanager_rotationschedule.py +21 -21
- localstack/services/secretsmanager/resource_providers/aws_secretsmanager_rotationschedule_plugin.py +1 -3
- localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secret.py +23 -23
- localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secret_plugin.py +1 -3
- localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secrettargetattachment.py +5 -5
- localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secrettargetattachment_plugin.py +1 -3
- localstack/services/ses/models.py +8 -1
- localstack/services/ses/provider.py +128 -55
- localstack/services/ses/resource_providers/aws_ses_emailidentity.py +21 -21
- localstack/services/ses/resource_providers/aws_ses_emailidentity_plugin.py +1 -3
- localstack/services/sns/constants.py +7 -1
- localstack/services/sns/executor.py +9 -2
- localstack/services/sns/models.py +25 -25
- localstack/services/sns/provider.py +9 -7
- localstack/services/sns/publisher.py +37 -23
- localstack/services/sns/resource_providers/aws_sns_subscription.py +13 -13
- localstack/services/sns/resource_providers/aws_sns_subscription_plugin.py +1 -3
- localstack/services/sns/resource_providers/aws_sns_topic.py +16 -16
- localstack/services/sns/resource_providers/aws_sns_topic_plugin.py +1 -3
- localstack/services/sns/resource_providers/aws_sns_topicpolicy.py +4 -4
- localstack/services/sns/resource_providers/aws_sns_topicpolicy_plugin.py +1 -3
- localstack/services/sns/v2/models.py +167 -0
- localstack/services/sns/v2/provider.py +867 -0
- localstack/services/sns/v2/utils.py +130 -0
- localstack/services/sqs/constants.py +2 -2
- localstack/services/sqs/developer_api.py +205 -0
- localstack/services/sqs/models.py +71 -36
- localstack/services/sqs/provider.py +56 -332
- localstack/services/sqs/query_api.py +8 -5
- localstack/services/sqs/resource_providers/aws_sqs_queue.py +21 -21
- localstack/services/sqs/resource_providers/aws_sqs_queue_plugin.py +1 -3
- localstack/services/sqs/resource_providers/aws_sqs_queuepolicy.py +4 -4
- localstack/services/sqs/resource_providers/aws_sqs_queuepolicy_plugin.py +1 -3
- localstack/services/sqs/utils.py +123 -4
- localstack/services/ssm/provider.py +3 -4
- localstack/services/ssm/resource_providers/aws_ssm_maintenancewindow.py +15 -15
- localstack/services/ssm/resource_providers/aws_ssm_maintenancewindow_plugin.py +1 -3
- localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtarget.py +10 -10
- localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtarget_plugin.py +1 -3
- localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtask.py +48 -48
- localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtask_plugin.py +1 -3
- localstack/services/ssm/resource_providers/aws_ssm_parameter.py +10 -10
- localstack/services/ssm/resource_providers/aws_ssm_parameter_plugin.py +1 -3
- localstack/services/ssm/resource_providers/aws_ssm_patchbaseline.py +29 -29
- localstack/services/ssm/resource_providers/aws_ssm_patchbaseline_plugin.py +1 -3
- localstack/services/stepfunctions/asl/antlt4utils/antlr4utils.py +3 -4
- localstack/services/stepfunctions/asl/component/common/assign/assign_decl.py +1 -1
- localstack/services/stepfunctions/asl/component/common/assign/assign_decl_binding.py +1 -1
- localstack/services/stepfunctions/asl/component/common/assign/assign_template_value_array.py +1 -1
- localstack/services/stepfunctions/asl/component/common/assign/assign_template_value_object.py +1 -1
- localstack/services/stepfunctions/asl/component/common/catch/catcher_decl.py +9 -9
- localstack/services/stepfunctions/asl/component/common/error_name/custom_error_name.py +2 -2
- localstack/services/stepfunctions/asl/component/common/error_name/error_name.py +4 -4
- localstack/services/stepfunctions/asl/component/common/error_name/failure_event.py +8 -8
- localstack/services/stepfunctions/asl/component/common/error_name/states_error_name_type.py +1 -1
- localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_binding.py +2 -2
- localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_value_array.py +1 -1
- localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_value_object.py +1 -1
- localstack/services/stepfunctions/asl/component/common/path/input_path.py +4 -4
- localstack/services/stepfunctions/asl/component/common/path/output_path.py +4 -4
- localstack/services/stepfunctions/asl/component/common/path/result_path.py +3 -3
- localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadarr/payload_arr.py +1 -1
- localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadbinding/payload_binding.py +3 -3
- localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadtmpl/payload_tmpl.py +1 -1
- localstack/services/stepfunctions/asl/component/common/retry/retrier_decl.py +8 -8
- localstack/services/stepfunctions/asl/component/common/string/string_expression.py +3 -3
- localstack/services/stepfunctions/asl/component/common/timeouts/timeout.py +3 -3
- localstack/services/stepfunctions/asl/component/eval_component.py +2 -3
- localstack/services/stepfunctions/asl/component/intrinsic/argument/argument.py +4 -4
- localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/array/array_partition.py +1 -1
- localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/generic/string_format.py +1 -1
- localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/states_function_array.py +1 -1
- localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/states_function_format.py +1 -1
- localstack/services/stepfunctions/asl/component/intrinsic/jsonata.py +3 -3
- localstack/services/stepfunctions/asl/component/intrinsic/member.py +1 -1
- localstack/services/stepfunctions/asl/component/program/program.py +9 -9
- localstack/services/stepfunctions/asl/component/program/states.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state.py +10 -11
- localstack/services/stepfunctions/asl/component/state/state_choice/choice_rule.py +11 -11
- localstack/services/stepfunctions/asl/component/state/state_choice/comparison/comparison.py +5 -11
- localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/implementations/is_operator.py +2 -2
- localstack/services/stepfunctions/asl/component/state/state_choice/state_choice.py +3 -5
- localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py +9 -9
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/item_reader_decl.py +7 -7
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/reader_config/reader_config_decl.py +5 -5
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/resource_eval/resource_eval_s3.py +2 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/resource_eval/resource_output_transformer/resource_output_transformer_csv.py +2 -2
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/distributed_iteration_component.py +8 -8
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/inline_iteration_component.py +7 -7
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/distributed_item_processor_worker.py +4 -4
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/inline_item_processor_worker.py +5 -5
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/map_run_record.py +10 -10
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_component.py +3 -3
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_declaration.py +3 -3
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_worker.py +2 -2
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/distributed_iterator_worker.py +3 -5
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/inline_iterator.py +1 -2
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/inline_iterator_worker.py +5 -5
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/job.py +9 -9
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/result_writer/resource_eval/resource_eval_s3.py +2 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +12 -13
- localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/branch_worker.py +3 -3
- localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/branches_decl.py +7 -7
- localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/state_parallel.py +2 -3
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py +7 -7
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/resource.py +3 -3
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py +10 -10
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_api_gateway.py +18 -18
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_aws_sdk.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_batch.py +8 -7
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py +18 -17
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_dynamodb.py +2 -2
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_ecs.py +5 -4
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_events.py +4 -4
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_glue.py +7 -6
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_lambda.py +4 -4
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sfn.py +9 -8
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sns.py +2 -2
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sqs.py +4 -4
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task.py +5 -5
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task_lambda.py +2 -2
- localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +3 -5
- localstack/services/stepfunctions/asl/component/state/state_pass/state_pass.py +5 -7
- localstack/services/stepfunctions/asl/component/state/state_succeed/state_succeed.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_wait/state_wait.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_wait/wait_function/timestamp.py +2 -2
- localstack/services/stepfunctions/asl/eval/callback/callback.py +18 -18
- localstack/services/stepfunctions/asl/eval/environment.py +22 -24
- localstack/services/stepfunctions/asl/eval/evaluation_details.py +3 -5
- localstack/services/stepfunctions/asl/eval/event/event_manager.py +10 -10
- localstack/services/stepfunctions/asl/eval/event/logging.py +3 -3
- localstack/services/stepfunctions/asl/eval/program_state.py +8 -8
- localstack/services/stepfunctions/asl/eval/states.py +12 -12
- localstack/services/stepfunctions/asl/eval/test_state/environment.py +3 -5
- localstack/services/stepfunctions/asl/eval/variable_store.py +6 -6
- localstack/services/stepfunctions/asl/jsonata/jsonata.py +7 -6
- localstack/services/stepfunctions/asl/parse/asl_parser.py +1 -1
- localstack/services/stepfunctions/asl/parse/intrinsic/preprocessor.py +2 -3
- localstack/services/stepfunctions/asl/parse/preprocessor.py +44 -44
- localstack/services/stepfunctions/asl/parse/typed_props.py +2 -2
- localstack/services/stepfunctions/asl/static_analyser/intrinsic/variable_names_intrinsic_static_analyser.py +1 -1
- localstack/services/stepfunctions/asl/static_analyser/variable_references_static_analyser.py +2 -2
- localstack/services/stepfunctions/asl/utils/encoding.py +2 -2
- localstack/services/stepfunctions/asl/utils/json_path.py +2 -2
- localstack/services/stepfunctions/backend/activity.py +4 -4
- localstack/services/stepfunctions/backend/alias.py +8 -8
- localstack/services/stepfunctions/backend/execution.py +29 -30
- localstack/services/stepfunctions/backend/execution_worker.py +7 -7
- localstack/services/stepfunctions/backend/state_machine.py +28 -28
- localstack/services/stepfunctions/backend/test_state/execution.py +3 -4
- localstack/services/stepfunctions/backend/test_state/execution_worker.py +1 -3
- localstack/services/stepfunctions/mocking/mock_config.py +9 -9
- localstack/services/stepfunctions/mocking/mock_config_file.py +10 -10
- localstack/services/stepfunctions/packages.py +14 -5
- localstack/services/stepfunctions/provider.py +34 -44
- localstack/services/stepfunctions/quotas.py +2 -3
- localstack/services/stepfunctions/resource_providers/aws_stepfunctions_activity.py +6 -6
- localstack/services/stepfunctions/resource_providers/aws_stepfunctions_activity_plugin.py +1 -3
- localstack/services/stepfunctions/resource_providers/aws_stepfunctions_statemachine.py +25 -25
- localstack/services/stepfunctions/resource_providers/aws_stepfunctions_statemachine_plugin.py +1 -3
- localstack/services/stepfunctions/stepfunctions_utils.py +1 -2
- localstack/services/stores.py +8 -8
- localstack/services/transcribe/packages.py +1 -3
- localstack/services/transcribe/provider.py +8 -3
- localstack/state/codecs.py +61 -0
- localstack/state/core.py +11 -5
- localstack/state/inspect.py +4 -4
- localstack/state/pickle.py +22 -60
- localstack/testing/aws/asf_utils.py +3 -2
- localstack/testing/aws/cloudformation_utils.py +1 -1
- localstack/testing/aws/lambda_utils.py +15 -14
- localstack/testing/aws/util.py +3 -2
- localstack/testing/pytest/cloudformation/fixtures.py +68 -18
- localstack/testing/pytest/container.py +5 -5
- localstack/testing/pytest/filters.py +1 -3
- localstack/testing/pytest/fixtures.py +188 -55
- localstack/testing/pytest/in_memory_localstack.py +1 -3
- localstack/testing/pytest/marking.py +42 -15
- localstack/testing/pytest/path_filter.py +1 -1
- localstack/testing/pytest/stepfunctions/fixtures.py +4 -4
- localstack/testing/pytest/stepfunctions/utils.py +11 -10
- localstack/testing/pytest/util.py +1 -1
- localstack/testing/pytest/validation_tracking.py +3 -4
- localstack/testing/scenario/provisioning.py +11 -10
- localstack/testing/snapshots/transformer_utility.py +8 -3
- localstack/testing/testselection/matching.py +2 -2
- localstack/testing/testselection/opt_out.py +1 -1
- localstack/testing/testselection/scripts/filter_by_test_selection.py +1 -1
- localstack/testing/testselection/scripts/generate_test_selection.py +1 -1
- localstack/testing/testselection/testselection.py +2 -2
- localstack/utils/analytics/cli.py +2 -3
- localstack/utils/analytics/client.py +5 -5
- localstack/utils/analytics/events.py +2 -2
- localstack/utils/analytics/metadata.py +6 -4
- localstack/utils/analytics/metrics/counter.py +11 -18
- localstack/utils/analytics/metrics/registry.py +2 -2
- localstack/utils/analytics/publisher.py +4 -5
- localstack/utils/analytics/service_providers.py +19 -0
- localstack/utils/analytics/service_request_aggregator.py +4 -4
- localstack/utils/archives.py +12 -12
- localstack/utils/asyncio.py +2 -2
- localstack/utils/aws/arns.py +26 -31
- localstack/utils/aws/aws_responses.py +21 -28
- localstack/utils/aws/aws_stack.py +7 -12
- localstack/utils/aws/dead_letter_queue.py +4 -9
- localstack/utils/aws/message_forwarding.py +8 -11
- localstack/utils/aws/request_context.py +5 -6
- localstack/utils/aws/resources.py +1 -1
- localstack/utils/aws/templating.py +4 -4
- localstack/utils/batch_policy.py +4 -4
- localstack/utils/bootstrap.py +37 -30
- localstack/utils/catalog/catalog.py +139 -0
- localstack/utils/catalog/catalog_loader.py +11 -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 +33 -27
- localstack/utils/config_listener.py +2 -2
- localstack/utils/container_networking.py +5 -6
- localstack/utils/container_utils/container_client.py +156 -160
- localstack/utils/container_utils/docker_cmd_client.py +97 -81
- localstack/utils/container_utils/docker_sdk_client.py +75 -72
- localstack/utils/crypto.py +12 -13
- localstack/utils/diagnose.py +11 -12
- localstack/utils/docker_utils.py +11 -7
- localstack/utils/files.py +34 -15
- localstack/utils/functions.py +5 -4
- localstack/utils/http.py +14 -14
- localstack/utils/iputils.py +2 -1
- localstack/utils/json.py +21 -7
- localstack/utils/kinesis/kinesis_connector.py +2 -1
- localstack/utils/net.py +25 -17
- localstack/utils/no_exit_argument_parser.py +2 -2
- localstack/utils/numbers.py +9 -2
- localstack/utils/objects.py +15 -14
- localstack/utils/patch.py +14 -7
- localstack/utils/platform.py +2 -2
- localstack/utils/run.py +15 -14
- localstack/utils/scheduler.py +13 -12
- localstack/utils/server/tcp_proxy.py +2 -2
- localstack/utils/serving.py +3 -4
- localstack/utils/strings.py +15 -16
- localstack/utils/sync.py +126 -1
- localstack/utils/tagging.py +10 -8
- localstack/utils/testutil.py +17 -17
- localstack/utils/threads.py +3 -3
- localstack/utils/time.py +12 -4
- localstack/utils/urls.py +1 -3
- localstack/utils/xml.py +1 -1
- localstack/utils/xray/traceid.py +1 -1
- localstack/version.py +16 -3
- {localstack_core-4.6.1.dev74.dist-info → localstack_core-4.10.1.dev7.dist-info}/METADATA +18 -14
- {localstack_core-4.6.1.dev74.dist-info → localstack_core-4.10.1.dev7.dist-info}/RECORD +663 -656
- {localstack_core-4.6.1.dev74.dist-info → localstack_core-4.10.1.dev7.dist-info}/entry_points.txt +8 -4
- localstack_core-4.10.1.dev7.dist-info/plux.json +1 -0
- localstack/packages/terraform.py +0 -47
- localstack/services/cloudformation/deploy.html +0 -144
- localstack/services/cloudformation/deploy_ui.py +0 -47
- localstack/services/cloudformation/plugins.py +0 -12
- localstack/services/lambda_/lambda_debug_mode/ldm.py +0 -375
- localstack/services/lambda_/lambda_debug_mode/ldm_config_file.py +0 -178
- localstack/services/lambda_/lambda_debug_mode/ldm_types.py +0 -11
- localstack/services/lambda_/lambda_debug_mode/ldm_utils.py +0 -43
- localstack_core-4.6.1.dev74.dist-info/plux.json +0 -1
- /localstack/{services/lambda_/lambda_debug_mode/__init__.py → testing/pytest/cloudformation/transformers.py} +0 -0
- {localstack_core-4.6.1.dev74.data → localstack_core-4.10.1.dev7.data}/scripts/localstack +0 -0
- {localstack_core-4.6.1.dev74.data → localstack_core-4.10.1.dev7.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.6.1.dev74.data → localstack_core-4.10.1.dev7.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.6.1.dev74.dist-info → localstack_core-4.10.1.dev7.dist-info}/WHEEL +0 -0
- {localstack_core-4.6.1.dev74.dist-info → localstack_core-4.10.1.dev7.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.6.1.dev74.dist-info → localstack_core-4.10.1.dev7.dist-info}/top_level.txt +0 -0
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
|
+
import re
|
|
4
5
|
from collections import defaultdict
|
|
5
|
-
from datetime import
|
|
6
|
-
from
|
|
6
|
+
from datetime import UTC, datetime
|
|
7
|
+
from urllib.parse import urlencode
|
|
7
8
|
|
|
9
|
+
from localstack import config
|
|
8
10
|
from localstack.aws.api import RequestContext, handler
|
|
9
11
|
from localstack.aws.api.cloudformation import (
|
|
12
|
+
AlreadyExistsException,
|
|
10
13
|
CallAs,
|
|
11
14
|
Changes,
|
|
12
15
|
ChangeSetNameOrId,
|
|
13
16
|
ChangeSetNotFoundException,
|
|
14
17
|
ChangeSetStatus,
|
|
18
|
+
ChangeSetSummary,
|
|
15
19
|
ChangeSetType,
|
|
16
20
|
ClientRequestToken,
|
|
17
21
|
CreateChangeSetInput,
|
|
@@ -43,16 +47,21 @@ from localstack.aws.api.cloudformation import (
|
|
|
43
47
|
IncludePropertyValues,
|
|
44
48
|
InsufficientCapabilitiesException,
|
|
45
49
|
InvalidChangeSetStatusException,
|
|
50
|
+
ListChangeSetsOutput,
|
|
51
|
+
ListExportsOutput,
|
|
46
52
|
ListStackResourcesOutput,
|
|
47
53
|
ListStacksOutput,
|
|
48
54
|
LogicalResourceId,
|
|
49
55
|
NextToken,
|
|
50
56
|
Parameter,
|
|
51
57
|
PhysicalResourceId,
|
|
58
|
+
ResourceStatus,
|
|
52
59
|
RetainExceptOnCreate,
|
|
53
60
|
RetainResources,
|
|
54
61
|
RoleARN,
|
|
55
62
|
RollbackConfiguration,
|
|
63
|
+
StackDriftInformation,
|
|
64
|
+
StackDriftStatus,
|
|
56
65
|
StackName,
|
|
57
66
|
StackNameOrId,
|
|
58
67
|
StackResourceDetail,
|
|
@@ -69,9 +78,14 @@ from localstack.aws.api.cloudformation import (
|
|
|
69
78
|
UpdateStackOutput,
|
|
70
79
|
UpdateTerminationProtectionOutput,
|
|
71
80
|
)
|
|
81
|
+
from localstack.aws.api.cloudformation import (
|
|
82
|
+
Stack as ApiStack,
|
|
83
|
+
)
|
|
72
84
|
from localstack.aws.connect import connect_to
|
|
73
85
|
from localstack.services.cloudformation import api_utils
|
|
74
86
|
from localstack.services.cloudformation.engine import template_preparer
|
|
87
|
+
from localstack.services.cloudformation.engine.parameters import resolve_ssm_parameter
|
|
88
|
+
from localstack.services.cloudformation.engine.transformers import FailedTransformationException
|
|
75
89
|
from localstack.services.cloudformation.engine.v2.change_set_model import (
|
|
76
90
|
ChangeSetModel,
|
|
77
91
|
ChangeType,
|
|
@@ -100,13 +114,25 @@ from localstack.services.cloudformation.stores import (
|
|
|
100
114
|
CloudFormationStore,
|
|
101
115
|
get_cloudformation_store,
|
|
102
116
|
)
|
|
103
|
-
from localstack.services.cloudformation.v2.entities import
|
|
117
|
+
from localstack.services.cloudformation.v2.entities import (
|
|
118
|
+
ChangeSet,
|
|
119
|
+
Stack,
|
|
120
|
+
StackInstance,
|
|
121
|
+
StackSet,
|
|
122
|
+
)
|
|
123
|
+
from localstack.services.cloudformation.v2.types import EngineParameter, engine_parameter_value
|
|
124
|
+
from localstack.services.plugins import ServiceLifecycleHook
|
|
104
125
|
from localstack.utils.collections import select_attributes
|
|
126
|
+
from localstack.utils.numbers import is_number
|
|
105
127
|
from localstack.utils.strings import short_uid
|
|
106
128
|
from localstack.utils.threads import start_worker_thread
|
|
107
129
|
|
|
108
130
|
LOG = logging.getLogger(__name__)
|
|
109
131
|
|
|
132
|
+
SSM_PARAMETER_TYPE_RE = re.compile(
|
|
133
|
+
r"^AWS::SSM::Parameter::Value<(?P<listtype>List<)?(?P<innertype>[^>]+)>?>$"
|
|
134
|
+
)
|
|
135
|
+
|
|
110
136
|
|
|
111
137
|
def is_stack_arn(stack_name_or_id: str) -> bool:
|
|
112
138
|
return ARN_STACK_REGEX.match(stack_name_or_id) is not None
|
|
@@ -159,7 +185,7 @@ def find_change_set_v2(
|
|
|
159
185
|
state: CloudFormationStore, change_set_name: str, stack_name: str | None = None
|
|
160
186
|
) -> ChangeSet | None:
|
|
161
187
|
if is_changeset_arn(change_set_name):
|
|
162
|
-
return state.change_sets
|
|
188
|
+
return state.change_sets.get(change_set_name)
|
|
163
189
|
else:
|
|
164
190
|
if stack_name is not None:
|
|
165
191
|
stack = find_stack_v2(state, stack_name)
|
|
@@ -171,7 +197,9 @@ def find_change_set_v2(
|
|
|
171
197
|
if change_set_candidate.change_set_name == change_set_name:
|
|
172
198
|
return change_set_candidate
|
|
173
199
|
else:
|
|
174
|
-
raise
|
|
200
|
+
raise ValidationError(
|
|
201
|
+
"StackName must be specified if ChangeSetName is not specified as an ARN."
|
|
202
|
+
)
|
|
175
203
|
|
|
176
204
|
|
|
177
205
|
def find_stack_set_v2(state: CloudFormationStore, stack_set_name: str) -> StackSet | None:
|
|
@@ -192,22 +220,143 @@ def find_stack_instance(stack_set: StackSet, account: str, region: str) -> Stack
|
|
|
192
220
|
return None
|
|
193
221
|
|
|
194
222
|
|
|
195
|
-
class CloudformationProviderV2(CloudformationProvider):
|
|
223
|
+
class CloudformationProviderV2(CloudformationProvider, ServiceLifecycleHook):
|
|
224
|
+
def on_before_start(self):
|
|
225
|
+
base = "https://github.com/localstack/localstack/issues/new"
|
|
226
|
+
query_args = {
|
|
227
|
+
"template": "bug-report.yml",
|
|
228
|
+
"labels": ",".join(
|
|
229
|
+
[
|
|
230
|
+
"aws:cloudformation:v2",
|
|
231
|
+
"status: triage needed",
|
|
232
|
+
"type: bug",
|
|
233
|
+
]
|
|
234
|
+
),
|
|
235
|
+
"title": "CFNV2: ",
|
|
236
|
+
}
|
|
237
|
+
issue_url = "?".join([base, urlencode(query_args)])
|
|
238
|
+
LOG.info(
|
|
239
|
+
"You have opted in to the new CloudFormation deployment engine. "
|
|
240
|
+
"You can opt in to using the old engine by setting PROVIDER_OVERRIDE_CLOUDFORMATION=engine-legacy. "
|
|
241
|
+
"If you experience issues, please submit a bug report at this URL: %s",
|
|
242
|
+
issue_url,
|
|
243
|
+
)
|
|
244
|
+
|
|
196
245
|
@staticmethod
|
|
246
|
+
def _resolve_parameters(
|
|
247
|
+
template: dict | None,
|
|
248
|
+
parameters: dict | None,
|
|
249
|
+
account_id: str,
|
|
250
|
+
region_name: str,
|
|
251
|
+
before_parameters: dict | None,
|
|
252
|
+
) -> dict[str, EngineParameter]:
|
|
253
|
+
template_parameters = template.get("Parameters", {})
|
|
254
|
+
resolved_parameters = {}
|
|
255
|
+
invalid_parameters = []
|
|
256
|
+
for name, parameter in template_parameters.items():
|
|
257
|
+
given_value = parameters.get(name)
|
|
258
|
+
default_value = parameter.get("Default")
|
|
259
|
+
resolved_parameter = EngineParameter(
|
|
260
|
+
type_=parameter["Type"],
|
|
261
|
+
given_value=given_value,
|
|
262
|
+
default_value=default_value,
|
|
263
|
+
no_echo=parameter.get("NoEcho"),
|
|
264
|
+
)
|
|
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
|
+
|
|
272
|
+
# TODO: support other parameter types
|
|
273
|
+
if match := SSM_PARAMETER_TYPE_RE.match(parameter["Type"]):
|
|
274
|
+
inner_type = match.group("innertype")
|
|
275
|
+
is_list_type = match.group("listtype") is not None
|
|
276
|
+
if is_list_type or inner_type == "CommaDelimitedList":
|
|
277
|
+
# list types
|
|
278
|
+
try:
|
|
279
|
+
resolved_value = resolve_ssm_parameter(
|
|
280
|
+
account_id, region_name, given_value or default_value
|
|
281
|
+
)
|
|
282
|
+
resolved_parameter["resolved_value"] = resolved_value.split(",")
|
|
283
|
+
except Exception:
|
|
284
|
+
raise ValidationError(
|
|
285
|
+
f"Parameter {name} should either have input value or default value"
|
|
286
|
+
)
|
|
287
|
+
else:
|
|
288
|
+
try:
|
|
289
|
+
resolved_parameter["resolved_value"] = resolve_ssm_parameter(
|
|
290
|
+
account_id, region_name, given_value or default_value
|
|
291
|
+
)
|
|
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
|
|
311
|
+
elif given_value is None and default_value is None:
|
|
312
|
+
invalid_parameters.append(name)
|
|
313
|
+
continue
|
|
314
|
+
|
|
315
|
+
resolved_parameters[name] = resolved_parameter
|
|
316
|
+
|
|
317
|
+
if invalid_parameters:
|
|
318
|
+
raise ValidationError(f"Parameters: [{','.join(invalid_parameters)}] must have values")
|
|
319
|
+
|
|
320
|
+
for name, parameter in resolved_parameters.items():
|
|
321
|
+
if (
|
|
322
|
+
parameter.get("resolved_value") is None
|
|
323
|
+
and parameter.get("given_value") is None
|
|
324
|
+
and parameter.get("default_value") is None
|
|
325
|
+
):
|
|
326
|
+
raise ValidationError(
|
|
327
|
+
f"Parameter {name} should either have input value or default value"
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
return resolved_parameters
|
|
331
|
+
|
|
332
|
+
@classmethod
|
|
197
333
|
def _setup_change_set_model(
|
|
334
|
+
cls,
|
|
198
335
|
change_set: ChangeSet,
|
|
199
|
-
before_template:
|
|
200
|
-
after_template:
|
|
201
|
-
before_parameters:
|
|
202
|
-
after_parameters:
|
|
203
|
-
previous_update_model:
|
|
336
|
+
before_template: dict | None,
|
|
337
|
+
after_template: dict | None,
|
|
338
|
+
before_parameters: dict | None,
|
|
339
|
+
after_parameters: dict | None,
|
|
340
|
+
previous_update_model: UpdateModel | None = None,
|
|
204
341
|
):
|
|
342
|
+
resolved_parameters = None
|
|
343
|
+
if after_parameters is not None:
|
|
344
|
+
resolved_parameters = cls._resolve_parameters(
|
|
345
|
+
after_template,
|
|
346
|
+
after_parameters,
|
|
347
|
+
change_set.stack.account_id,
|
|
348
|
+
change_set.stack.region_name,
|
|
349
|
+
before_parameters,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
change_set.resolved_parameters = resolved_parameters
|
|
353
|
+
|
|
205
354
|
# Create and preprocess the update graph for this template update.
|
|
206
355
|
change_set_model = ChangeSetModel(
|
|
207
356
|
before_template=before_template,
|
|
208
357
|
after_template=after_template,
|
|
209
358
|
before_parameters=before_parameters,
|
|
210
|
-
after_parameters=
|
|
359
|
+
after_parameters=resolved_parameters,
|
|
211
360
|
)
|
|
212
361
|
raw_update_model: UpdateModel = change_set_model.get_update_model()
|
|
213
362
|
# If there exists an update model which operated in the 'before' version of this change set,
|
|
@@ -222,20 +371,29 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
222
371
|
change_set_model_transform = ChangeSetModelTransform(
|
|
223
372
|
change_set=change_set,
|
|
224
373
|
before_parameters=before_parameters,
|
|
225
|
-
after_parameters=
|
|
374
|
+
after_parameters=resolved_parameters,
|
|
226
375
|
before_template=before_template,
|
|
227
376
|
after_template=after_template,
|
|
228
377
|
)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
378
|
+
try:
|
|
379
|
+
transformed_before_template, transformed_after_template = (
|
|
380
|
+
change_set_model_transform.transform()
|
|
381
|
+
)
|
|
382
|
+
except FailedTransformationException as e:
|
|
383
|
+
change_set.status = ChangeSetStatus.FAILED
|
|
384
|
+
change_set.status_reason = e.message
|
|
385
|
+
change_set.stack.set_stack_status(
|
|
386
|
+
status=StackStatus.ROLLBACK_IN_PROGRESS, reason=e.message
|
|
387
|
+
)
|
|
388
|
+
change_set.stack.set_stack_status(status=StackStatus.CREATE_FAILED)
|
|
389
|
+
return
|
|
232
390
|
|
|
233
391
|
# Remodel the update graph after the applying the global transforms.
|
|
234
392
|
change_set_model = ChangeSetModel(
|
|
235
393
|
before_template=transformed_before_template,
|
|
236
394
|
after_template=transformed_after_template,
|
|
237
395
|
before_parameters=before_parameters,
|
|
238
|
-
after_parameters=
|
|
396
|
+
after_parameters=resolved_parameters,
|
|
239
397
|
)
|
|
240
398
|
update_model = change_set_model.get_update_model()
|
|
241
399
|
# Bring the cache for the previous operations forward in the update graph for this version
|
|
@@ -244,6 +402,7 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
244
402
|
# the transformations.
|
|
245
403
|
update_model.before_runtime_cache.update(raw_update_model.before_runtime_cache)
|
|
246
404
|
update_model.after_runtime_cache.update(raw_update_model.after_runtime_cache)
|
|
405
|
+
change_set.set_update_model(update_model)
|
|
247
406
|
|
|
248
407
|
# perform validations
|
|
249
408
|
validator = ChangeSetModelValidator(
|
|
@@ -251,16 +410,19 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
251
410
|
)
|
|
252
411
|
validator.validate()
|
|
253
412
|
|
|
254
|
-
|
|
255
|
-
|
|
413
|
+
# hacky
|
|
414
|
+
if transform := raw_update_model.node_template.transform:
|
|
415
|
+
if transform.global_transforms:
|
|
416
|
+
# global transforms should always be considered "MODIFIED"
|
|
417
|
+
update_model.node_template.change_type = ChangeType.MODIFIED
|
|
418
|
+
change_set.processed_template = transformed_after_template
|
|
256
419
|
|
|
257
420
|
@handler("CreateChangeSet", expand=False)
|
|
258
421
|
def create_change_set(
|
|
259
422
|
self, context: RequestContext, request: CreateChangeSetInput
|
|
260
423
|
) -> CreateChangeSetOutput:
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
except KeyError:
|
|
424
|
+
stack_name = request.get("StackName")
|
|
425
|
+
if not stack_name:
|
|
264
426
|
# TODO: proper exception
|
|
265
427
|
raise ValidationError("StackName must be specified")
|
|
266
428
|
try:
|
|
@@ -290,12 +452,19 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
290
452
|
template_body = api_utils.extract_template_body(request)
|
|
291
453
|
structured_template = template_preparer.parse_template(template_body)
|
|
292
454
|
|
|
455
|
+
if len(template_body) > 51200 and not template_url:
|
|
456
|
+
raise ValidationError(
|
|
457
|
+
f"1 validation error detected: Value '{template_body}' at 'templateBody' "
|
|
458
|
+
"failed to satisfy constraint: Member must have length less than or equal to 51200"
|
|
459
|
+
)
|
|
460
|
+
|
|
293
461
|
# this is intentionally not in a util yet. Let's first see how the different operations deal with these before generalizing
|
|
294
462
|
# handle ARN stack_name here (not valid for initial CREATE, since stack doesn't exist yet)
|
|
295
463
|
if is_stack_arn(stack_name):
|
|
296
464
|
stack = state.stacks_v2.get(stack_name)
|
|
297
465
|
if not stack:
|
|
298
466
|
raise ValidationError(f"Stack '{stack_name}' does not exist.")
|
|
467
|
+
stack.capabilities = request.get("Capabilities") or []
|
|
299
468
|
else:
|
|
300
469
|
# stack name specified, so fetch the stack by name
|
|
301
470
|
stack_candidates: list[Stack] = [
|
|
@@ -309,8 +478,6 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
309
478
|
account_id=context.account_id,
|
|
310
479
|
region_name=context.region,
|
|
311
480
|
request_payload=request,
|
|
312
|
-
template=structured_template,
|
|
313
|
-
template_body=template_body,
|
|
314
481
|
initial_status=StackStatus.REVIEW_IN_PROGRESS,
|
|
315
482
|
)
|
|
316
483
|
state.stacks_v2[stack.stack_id] = stack
|
|
@@ -318,6 +485,8 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
318
485
|
if not active_stack_candidates:
|
|
319
486
|
raise ValidationError(f"Stack '{stack_name}' does not exist.")
|
|
320
487
|
stack = active_stack_candidates[0]
|
|
488
|
+
# propagate capabilities from create change set request
|
|
489
|
+
stack.capabilities = request.get("Capabilities") or []
|
|
321
490
|
|
|
322
491
|
# TODO: test if rollback status is allowed as well
|
|
323
492
|
if (
|
|
@@ -328,6 +497,14 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
328
497
|
f"Stack [{stack_name}] already exists and cannot be created again with the changeSet [{change_set_name}]."
|
|
329
498
|
)
|
|
330
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
|
+
|
|
331
508
|
before_parameters: dict[str, Parameter] | None = None
|
|
332
509
|
match change_set_type:
|
|
333
510
|
case ChangeSetType.UPDATE:
|
|
@@ -352,12 +529,9 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
352
529
|
# The options might be reduce to using the current style, or passing the extra information
|
|
353
530
|
# as a metadata object. The choice should be made considering when the extra information
|
|
354
531
|
# is needed for the update graph building, or only looked up in downstream tasks (metadata).
|
|
355
|
-
request_parameters = request.get("Parameters",
|
|
532
|
+
request_parameters = request.get("Parameters", [])
|
|
356
533
|
# TODO: handle parameter defaults and resolution
|
|
357
|
-
after_parameters
|
|
358
|
-
parameter["ParameterKey"]: parameter["ParameterValue"]
|
|
359
|
-
for parameter in request_parameters
|
|
360
|
-
}
|
|
534
|
+
after_parameters = self._extract_after_parameters(request_parameters, before_parameters)
|
|
361
535
|
|
|
362
536
|
# TODO: update this logic to always pass the clean template object if one exists. The
|
|
363
537
|
# current issue with relaying on stack.template_original is that this appears to have
|
|
@@ -377,7 +551,12 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
377
551
|
pass
|
|
378
552
|
|
|
379
553
|
# create change set for the stack and apply changes
|
|
380
|
-
change_set = ChangeSet(
|
|
554
|
+
change_set = ChangeSet(
|
|
555
|
+
stack,
|
|
556
|
+
request,
|
|
557
|
+
template=after_template,
|
|
558
|
+
template_body=template_body,
|
|
559
|
+
)
|
|
381
560
|
self._setup_change_set_model(
|
|
382
561
|
change_set=change_set,
|
|
383
562
|
before_template=before_template,
|
|
@@ -386,24 +565,21 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
386
565
|
after_parameters=after_parameters,
|
|
387
566
|
previous_update_model=previous_update_model,
|
|
388
567
|
)
|
|
389
|
-
|
|
390
|
-
# TODO: handle the empty change set case
|
|
391
|
-
if not change_set.has_changes():
|
|
392
|
-
change_set.set_change_set_status(ChangeSetStatus.FAILED)
|
|
568
|
+
if change_set.status == ChangeSetStatus.FAILED:
|
|
393
569
|
change_set.set_execution_status(ExecutionStatus.UNAVAILABLE)
|
|
394
|
-
change_set.status_reason = "The submitted information didn't contain changes. Submit different information to create a change set."
|
|
395
570
|
else:
|
|
396
|
-
if
|
|
397
|
-
|
|
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."
|
|
398
575
|
else:
|
|
399
|
-
stack.
|
|
576
|
+
if stack.status not in [StackStatus.CREATE_COMPLETE, StackStatus.UPDATE_COMPLETE]:
|
|
577
|
+
stack.set_stack_status(StackStatus.REVIEW_IN_PROGRESS, "User Initiated")
|
|
400
578
|
|
|
401
|
-
|
|
579
|
+
change_set.set_change_set_status(ChangeSetStatus.CREATE_COMPLETE)
|
|
402
580
|
|
|
403
|
-
stack.
|
|
404
|
-
stack.change_set_ids.append(change_set.change_set_id)
|
|
581
|
+
stack.change_set_ids.add(change_set.change_set_id)
|
|
405
582
|
state.change_sets[change_set.change_set_id] = change_set
|
|
406
|
-
|
|
407
583
|
return CreateChangeSetOutput(StackId=stack.stack_id, Id=change_set.change_set_id)
|
|
408
584
|
|
|
409
585
|
@handler("ExecuteChangeSet")
|
|
@@ -438,6 +614,8 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
438
614
|
raise RuntimeError("Programming error: no update graph found for change set")
|
|
439
615
|
|
|
440
616
|
change_set.set_execution_status(ExecutionStatus.EXECUTE_IN_PROGRESS)
|
|
617
|
+
# propagate the tags as this is done during execution
|
|
618
|
+
change_set.stack.tags = change_set.tags
|
|
441
619
|
change_set.stack.set_stack_status(
|
|
442
620
|
StackStatus.UPDATE_IN_PROGRESS
|
|
443
621
|
if change_set.change_set_type == ChangeSetType.UPDATE
|
|
@@ -449,37 +627,104 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
449
627
|
)
|
|
450
628
|
|
|
451
629
|
def _run(*args):
|
|
452
|
-
|
|
453
|
-
|
|
630
|
+
# TODO: should this be cleared before or after execution?
|
|
631
|
+
change_set.stack.status_reason = None
|
|
632
|
+
result = change_set_executor.execute()
|
|
633
|
+
change_set.stack.resolved_parameters = change_set.resolved_parameters
|
|
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
|
+
|
|
640
|
+
if not result.failure_message:
|
|
454
641
|
new_stack_status = StackStatus.UPDATE_COMPLETE
|
|
455
642
|
if change_set.change_set_type == ChangeSetType.CREATE:
|
|
456
643
|
new_stack_status = StackStatus.CREATE_COMPLETE
|
|
457
644
|
change_set.stack.set_stack_status(new_stack_status)
|
|
458
645
|
change_set.set_execution_status(ExecutionStatus.EXECUTE_COMPLETE)
|
|
459
|
-
change_set.stack.resolved_resources = result.resources
|
|
460
|
-
change_set.stack.resolved_parameters = result.parameters
|
|
461
646
|
change_set.stack.resolved_outputs = result.outputs
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
647
|
+
|
|
648
|
+
change_set.stack.resolved_exports = {}
|
|
649
|
+
for output in result.outputs:
|
|
650
|
+
if export_name := output.get("ExportName"):
|
|
651
|
+
change_set.stack.resolved_exports[export_name] = output["OutputValue"]
|
|
652
|
+
|
|
653
|
+
change_set.stack.change_set_id = change_set.change_set_id
|
|
654
|
+
else:
|
|
466
655
|
LOG.error(
|
|
467
|
-
"Execute change set failed: %s",
|
|
656
|
+
"Execute change set failed: %s",
|
|
657
|
+
result.failure_message,
|
|
658
|
+
exc_info=LOG.isEnabledFor(logging.DEBUG) and config.CFN_VERBOSE_ERRORS,
|
|
468
659
|
)
|
|
469
|
-
|
|
470
|
-
if change_set.change_set_type == ChangeSetType.CREATE:
|
|
471
|
-
new_stack_status = StackStatus.CREATE_FAILED
|
|
472
|
-
|
|
473
|
-
change_set.stack.set_stack_status(new_stack_status)
|
|
660
|
+
# stack status is taken care of in the executor
|
|
474
661
|
change_set.set_execution_status(ExecutionStatus.EXECUTE_FAILED)
|
|
662
|
+
change_set.stack.deletion_time = datetime.now(tz=UTC)
|
|
475
663
|
|
|
476
664
|
start_worker_thread(_run)
|
|
477
665
|
|
|
478
666
|
return ExecuteChangeSetOutput()
|
|
479
667
|
|
|
480
|
-
|
|
481
|
-
|
|
668
|
+
@staticmethod
|
|
669
|
+
def _render_resolved_parameters(
|
|
670
|
+
resolved_parameters: dict[str, EngineParameter],
|
|
671
|
+
) -> list[Parameter]:
|
|
672
|
+
result = []
|
|
673
|
+
for name, resolved_parameter in resolved_parameters.items():
|
|
674
|
+
parameter = Parameter(
|
|
675
|
+
ParameterKey=name,
|
|
676
|
+
ParameterValue=resolved_parameter.get("given_value")
|
|
677
|
+
or resolved_parameter.get("default_value"),
|
|
678
|
+
)
|
|
679
|
+
if resolved_value := resolved_parameter.get("resolved_value"):
|
|
680
|
+
parameter["ResolvedValue"] = resolved_value
|
|
681
|
+
|
|
682
|
+
# TODO :what happens to the resolved value?
|
|
683
|
+
if resolved_parameter.get("no_echo", False):
|
|
684
|
+
parameter["ParameterValue"] = "****"
|
|
685
|
+
result.append(parameter)
|
|
686
|
+
|
|
687
|
+
return result
|
|
688
|
+
|
|
689
|
+
@handler("DescribeChangeSet")
|
|
690
|
+
def describe_change_set(
|
|
691
|
+
self,
|
|
692
|
+
context: RequestContext,
|
|
693
|
+
change_set_name: ChangeSetNameOrId,
|
|
694
|
+
stack_name: StackNameOrId | None = None,
|
|
695
|
+
next_token: NextToken | None = None,
|
|
696
|
+
include_property_values: IncludePropertyValues | None = None,
|
|
697
|
+
**kwargs,
|
|
482
698
|
) -> DescribeChangeSetOutput:
|
|
699
|
+
# TODO add support for include_property_values
|
|
700
|
+
# only relevant if change_set_name isn't an ARN
|
|
701
|
+
state = get_cloudformation_store(context.account_id, context.region)
|
|
702
|
+
change_set = find_change_set_v2(state, change_set_name, stack_name)
|
|
703
|
+
|
|
704
|
+
if not change_set:
|
|
705
|
+
raise ChangeSetNotFoundException(f"ChangeSet [{change_set_name}] does not exist")
|
|
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
|
+
|
|
483
728
|
# TODO: The ChangeSetModelDescriber currently matches AWS behavior by listing
|
|
484
729
|
# resource changes in the order they appear in the template. However, when
|
|
485
730
|
# a resource change is triggered indirectly (e.g., via Ref or GetAtt), the
|
|
@@ -501,38 +746,56 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
501
746
|
StackId=change_set.stack.stack_id,
|
|
502
747
|
StackName=change_set.stack.stack_name,
|
|
503
748
|
CreationTime=change_set.creation_time,
|
|
504
|
-
Parameters=[
|
|
505
|
-
# TODO: add masking support.
|
|
506
|
-
Parameter(ParameterKey=key, ParameterValue=value)
|
|
507
|
-
for (key, value) in change_set.stack.resolved_parameters.items()
|
|
508
|
-
],
|
|
509
749
|
Changes=changes,
|
|
510
750
|
Capabilities=change_set.stack.capabilities,
|
|
511
751
|
StatusReason=change_set.status_reason,
|
|
752
|
+
Description=change_set.description,
|
|
753
|
+
# TODO: static information
|
|
754
|
+
IncludeNestedStacks=False,
|
|
755
|
+
NotificationARNs=[],
|
|
756
|
+
Tags=change_set.tags or None,
|
|
512
757
|
)
|
|
758
|
+
if change_set.resolved_parameters:
|
|
759
|
+
result["Parameters"] = self._render_resolved_parameters(change_set.resolved_parameters)
|
|
513
760
|
return result
|
|
514
761
|
|
|
515
|
-
@handler("
|
|
516
|
-
def
|
|
762
|
+
@handler("ListChangeSets")
|
|
763
|
+
def list_change_sets(
|
|
517
764
|
self,
|
|
518
765
|
context: RequestContext,
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
next_token: NextToken | None = None,
|
|
522
|
-
include_property_values: IncludePropertyValues | None = None,
|
|
766
|
+
stack_name: StackNameOrId,
|
|
767
|
+
next_token: NextToken = None,
|
|
523
768
|
**kwargs,
|
|
524
|
-
) ->
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
+
)
|
|
529
797
|
|
|
530
|
-
|
|
531
|
-
raise ChangeSetNotFoundException(f"ChangeSet [{change_set_name}] does not exist")
|
|
532
|
-
result = self._describe_change_set(
|
|
533
|
-
change_set=change_set, include_property_values=include_property_values or False
|
|
534
|
-
)
|
|
535
|
-
return result
|
|
798
|
+
return ListChangeSetsOutput(Summaries=summaries)
|
|
536
799
|
|
|
537
800
|
@handler("DeleteChangeSet")
|
|
538
801
|
def delete_change_set(
|
|
@@ -543,21 +806,26 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
543
806
|
**kwargs,
|
|
544
807
|
) -> DeleteChangeSetOutput:
|
|
545
808
|
state = get_cloudformation_store(context.account_id, context.region)
|
|
546
|
-
|
|
547
|
-
if is_changeset_arn(change_set_name):
|
|
548
|
-
change_set = state.change_sets.get(change_set_name)
|
|
549
|
-
elif not is_changeset_arn(change_set_name) and stack_name:
|
|
550
|
-
change_set = find_change_set_v2(state, change_set_name, stack_name)
|
|
551
|
-
else:
|
|
552
|
-
raise ValidationError(
|
|
553
|
-
"StackName must be specified if ChangeSetName is not specified as an ARN."
|
|
554
|
-
)
|
|
555
|
-
|
|
809
|
+
change_set = find_change_set_v2(state, change_set_name, stack_name)
|
|
556
810
|
if not change_set:
|
|
557
811
|
return DeleteChangeSetOutput()
|
|
558
812
|
|
|
559
|
-
|
|
560
|
-
|
|
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
|
+
)
|
|
561
829
|
|
|
562
830
|
return DeleteChangeSetOutput()
|
|
563
831
|
|
|
@@ -570,6 +838,26 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
570
838
|
raise ValidationError("StackName must be specified")
|
|
571
839
|
|
|
572
840
|
state = get_cloudformation_store(context.account_id, context.region)
|
|
841
|
+
|
|
842
|
+
active_stack_candidates = [
|
|
843
|
+
stack
|
|
844
|
+
for stack in state.stacks_v2.values()
|
|
845
|
+
if stack.stack_name == stack_name and stack.status not in [StackStatus.DELETE_COMPLETE]
|
|
846
|
+
]
|
|
847
|
+
|
|
848
|
+
# TODO: fix/implement this code path
|
|
849
|
+
# this needs more investigation how Cloudformation handles it (e.g. normal stack create or does it create a separate changeset?)
|
|
850
|
+
# REVIEW_IN_PROGRESS is another special status
|
|
851
|
+
# in this case existing changesets are set to obsolete and the stack is created
|
|
852
|
+
# review_stack_candidates = [s for s in stack_candidates if s.status == StackStatus.REVIEW_IN_PROGRESS]
|
|
853
|
+
# if review_stack_candidates:
|
|
854
|
+
# set changesets to obsolete
|
|
855
|
+
# for cs in review_stack_candidates[0].change_sets:
|
|
856
|
+
# cs.execution_status = ExecutionStatus.OBSOLETE
|
|
857
|
+
|
|
858
|
+
if active_stack_candidates:
|
|
859
|
+
raise AlreadyExistsException(f"Stack [{stack_name}] already exists")
|
|
860
|
+
|
|
573
861
|
# TODO: copied from create_change_set, consider unifying
|
|
574
862
|
template_body = request.get("TemplateBody")
|
|
575
863
|
# s3 or secretsmanager url
|
|
@@ -589,6 +877,12 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
589
877
|
template_body = api_utils.extract_template_body(request)
|
|
590
878
|
structured_template = template_preparer.parse_template(template_body)
|
|
591
879
|
|
|
880
|
+
if len(template_body) > 51200 and not template_url:
|
|
881
|
+
raise ValidationError(
|
|
882
|
+
f"1 validation error detected: Value '{template_body}' at 'templateBody' "
|
|
883
|
+
"failed to satisfy constraint: Member must have length less than or equal to 51200"
|
|
884
|
+
)
|
|
885
|
+
|
|
592
886
|
if "CAPABILITY_AUTO_EXPAND" not in request.get("Capabilities", []) and (
|
|
593
887
|
"Transform" in structured_template.keys() or "Fn::Transform" in template_body
|
|
594
888
|
):
|
|
@@ -600,8 +894,7 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
600
894
|
account_id=context.account_id,
|
|
601
895
|
region_name=context.region,
|
|
602
896
|
request_payload=request,
|
|
603
|
-
|
|
604
|
-
template_body=template_body,
|
|
897
|
+
tags=request.get("Tags"),
|
|
605
898
|
)
|
|
606
899
|
# TODO: what is the correct initial status?
|
|
607
900
|
state.stacks_v2[stack.stack_id] = stack
|
|
@@ -610,12 +903,9 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
610
903
|
# The options might be reduce to using the current style, or passing the extra information
|
|
611
904
|
# as a metadata object. The choice should be made considering when the extra information
|
|
612
905
|
# is needed for the update graph building, or only looked up in downstream tasks (metadata).
|
|
613
|
-
request_parameters = request.get("Parameters",
|
|
906
|
+
request_parameters = request.get("Parameters", [])
|
|
614
907
|
# TODO: handle parameter defaults and resolution
|
|
615
|
-
after_parameters
|
|
616
|
-
parameter["ParameterKey"]: parameter["ParameterValue"]
|
|
617
|
-
for parameter in request_parameters
|
|
618
|
-
}
|
|
908
|
+
after_parameters = self._extract_after_parameters(request_parameters)
|
|
619
909
|
after_template = structured_template
|
|
620
910
|
|
|
621
911
|
# Create internal change set to execute
|
|
@@ -623,6 +913,7 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
623
913
|
stack,
|
|
624
914
|
{"ChangeSetName": f"cs-{stack_name}-create", "ChangeSetType": ChangeSetType.CREATE},
|
|
625
915
|
template=after_template,
|
|
916
|
+
template_body=template_body,
|
|
626
917
|
)
|
|
627
918
|
self._setup_change_set_model(
|
|
628
919
|
change_set=change_set,
|
|
@@ -632,6 +923,10 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
632
923
|
after_parameters=after_parameters,
|
|
633
924
|
previous_update_model=None,
|
|
634
925
|
)
|
|
926
|
+
if change_set.status == ChangeSetStatus.FAILED:
|
|
927
|
+
return CreateStackOutput(StackId=stack.stack_id)
|
|
928
|
+
|
|
929
|
+
stack.processed_template = change_set.processed_template
|
|
635
930
|
|
|
636
931
|
# deployment process
|
|
637
932
|
stack.set_stack_status(StackStatus.CREATE_IN_PROGRESS)
|
|
@@ -640,16 +935,31 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
640
935
|
def _run(*args):
|
|
641
936
|
try:
|
|
642
937
|
result = change_set_executor.execute()
|
|
643
|
-
stack.set_stack_status(StackStatus.CREATE_COMPLETE)
|
|
644
938
|
stack.resolved_resources = result.resources
|
|
645
|
-
stack.resolved_parameters = result.parameters
|
|
646
939
|
stack.resolved_outputs = result.outputs
|
|
940
|
+
if all(
|
|
941
|
+
resource["ResourceStatus"] == ResourceStatus.CREATE_COMPLETE
|
|
942
|
+
for resource in stack.resolved_resources.values()
|
|
943
|
+
):
|
|
944
|
+
stack.set_stack_status(StackStatus.CREATE_COMPLETE)
|
|
945
|
+
else:
|
|
946
|
+
stack.set_stack_status(StackStatus.CREATE_FAILED)
|
|
947
|
+
|
|
647
948
|
# if the deployment succeeded, update the stack's template representation to that
|
|
648
949
|
# which was just deployed
|
|
649
950
|
stack.template = change_set.template
|
|
951
|
+
stack.template_body = change_set.template_body
|
|
952
|
+
stack.processed_template = change_set.processed_template
|
|
953
|
+
stack.resolved_parameters = change_set.resolved_parameters
|
|
954
|
+
stack.resolved_exports = {}
|
|
955
|
+
for output in result.outputs:
|
|
956
|
+
if export_name := output.get("ExportName"):
|
|
957
|
+
stack.resolved_exports[export_name] = output["OutputValue"]
|
|
650
958
|
except Exception as e:
|
|
651
959
|
LOG.error(
|
|
652
|
-
"Create Stack set failed: %s",
|
|
960
|
+
"Create Stack set failed: %s",
|
|
961
|
+
e,
|
|
962
|
+
exc_info=LOG.isEnabledFor(logging.WARNING) and config.CFN_VERBOSE_ERRORS,
|
|
653
963
|
)
|
|
654
964
|
stack.set_stack_status(StackStatus.CREATE_FAILED)
|
|
655
965
|
|
|
@@ -676,11 +986,56 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
676
986
|
**kwargs,
|
|
677
987
|
) -> DescribeStacksOutput:
|
|
678
988
|
state = get_cloudformation_store(context.account_id, context.region)
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
989
|
+
if stack_name:
|
|
990
|
+
stack = find_stack_v2(state, stack_name)
|
|
991
|
+
if not stack:
|
|
992
|
+
raise ValidationError(f"Stack with id {stack_name} does not exist")
|
|
993
|
+
stacks = [stack]
|
|
994
|
+
else:
|
|
995
|
+
stacks = state.stacks_v2.values()
|
|
996
|
+
|
|
997
|
+
describe_stack_output: list[ApiStack] = []
|
|
998
|
+
for stack in stacks:
|
|
999
|
+
describe_stack_output.append(self._describe_stack(stack))
|
|
1000
|
+
|
|
1001
|
+
return DescribeStacksOutput(Stacks=describe_stack_output)
|
|
1002
|
+
|
|
1003
|
+
def _describe_stack(self, stack: Stack) -> ApiStack:
|
|
1004
|
+
stack_description = ApiStack(
|
|
1005
|
+
Description=stack.description,
|
|
1006
|
+
CreationTime=stack.creation_time,
|
|
1007
|
+
StackId=stack.stack_id,
|
|
1008
|
+
StackName=stack.stack_name,
|
|
1009
|
+
StackStatus=stack.status,
|
|
1010
|
+
StackStatusReason=stack.status_reason,
|
|
1011
|
+
# fake values
|
|
1012
|
+
DisableRollback=False,
|
|
1013
|
+
DriftInformation=StackDriftInformation(StackDriftStatus=StackDriftStatus.NOT_CHECKED),
|
|
1014
|
+
EnableTerminationProtection=stack.enable_termination_protection,
|
|
1015
|
+
RollbackConfiguration=RollbackConfiguration(),
|
|
1016
|
+
Tags=stack.tags,
|
|
1017
|
+
NotificationARNs=[],
|
|
1018
|
+
)
|
|
1019
|
+
if stack.status != StackStatus.REVIEW_IN_PROGRESS:
|
|
1020
|
+
# TODO: actually track updated time
|
|
1021
|
+
stack_description["LastUpdatedTime"] = stack.creation_time
|
|
1022
|
+
if stack.deletion_time:
|
|
1023
|
+
stack_description["DeletionTime"] = stack.deletion_time
|
|
1024
|
+
if stack.capabilities:
|
|
1025
|
+
stack_description["Capabilities"] = stack.capabilities
|
|
1026
|
+
# TODO: confirm the logic for this
|
|
1027
|
+
if change_set_id := stack.change_set_id:
|
|
1028
|
+
stack_description["ChangeSetId"] = change_set_id
|
|
1029
|
+
|
|
1030
|
+
if stack.resolved_parameters:
|
|
1031
|
+
stack_description["Parameters"] = self._render_resolved_parameters(
|
|
1032
|
+
stack.resolved_parameters
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
if stack.resolved_outputs:
|
|
1036
|
+
stack_description["Outputs"] = stack.resolved_outputs
|
|
1037
|
+
|
|
1038
|
+
return stack_description
|
|
684
1039
|
|
|
685
1040
|
@handler("ListStacks")
|
|
686
1041
|
def list_stacks(
|
|
@@ -693,7 +1048,7 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
693
1048
|
state = get_cloudformation_store(context.account_id, context.region)
|
|
694
1049
|
|
|
695
1050
|
stacks = [
|
|
696
|
-
|
|
1051
|
+
self._describe_stack(s)
|
|
697
1052
|
for s in state.stacks_v2.values()
|
|
698
1053
|
if not stack_status_filter or s.status in stack_status_filter
|
|
699
1054
|
]
|
|
@@ -754,6 +1109,12 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
754
1109
|
|
|
755
1110
|
try:
|
|
756
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
|
|
757
1118
|
except KeyError:
|
|
758
1119
|
raise ValidationError(
|
|
759
1120
|
f"Resource {logical_resource_id} does not exist for stack {stack_name}"
|
|
@@ -767,6 +1128,7 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
767
1128
|
ResourceType=resource["Type"],
|
|
768
1129
|
LastUpdatedTimestamp=resource["LastUpdatedTimestamp"],
|
|
769
1130
|
ResourceStatus=resource["ResourceStatus"],
|
|
1131
|
+
DriftInformation={"StackResourceDriftStatus": "NOT_CHECKED"},
|
|
770
1132
|
)
|
|
771
1133
|
return DescribeStackResourceOutput(StackResourceDetail=resource_detail)
|
|
772
1134
|
|
|
@@ -1012,10 +1374,19 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
1012
1374
|
) -> GetTemplateOutput:
|
|
1013
1375
|
state = get_cloudformation_store(context.account_id, context.region)
|
|
1014
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
|
+
|
|
1015
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")
|
|
1016
1383
|
stack = change_set.stack
|
|
1017
1384
|
elif stack_name:
|
|
1018
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
|
+
)
|
|
1019
1390
|
else:
|
|
1020
1391
|
raise StackNotFoundError(stack_name)
|
|
1021
1392
|
|
|
@@ -1042,6 +1413,12 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
1042
1413
|
stack = find_stack_v2(state, stack_name)
|
|
1043
1414
|
if not stack:
|
|
1044
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
|
+
|
|
1045
1422
|
template = stack.template
|
|
1046
1423
|
else:
|
|
1047
1424
|
template_body = request.get("TemplateBody")
|
|
@@ -1063,6 +1440,11 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
1063
1440
|
template = template_preparer.parse_template(template_body)
|
|
1064
1441
|
|
|
1065
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
|
+
|
|
1066
1448
|
for resource_id, resource in template["Resources"].items():
|
|
1067
1449
|
res_type = resource["Type"]
|
|
1068
1450
|
id_summaries[res_type].append(resource_id)
|
|
@@ -1166,18 +1548,24 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
1166
1548
|
raise RuntimeError("Multiple stacks matched, update matching logic")
|
|
1167
1549
|
stack = active_stack_candidates[0]
|
|
1168
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
|
+
|
|
1169
1559
|
# TODO: proper status modeling
|
|
1170
1560
|
before_parameters = stack.resolved_parameters
|
|
1171
1561
|
# TODO: reconsider the way parameters are modelled in the update graph process.
|
|
1172
1562
|
# The options might be reduce to using the current style, or passing the extra information
|
|
1173
1563
|
# as a metadata object. The choice should be made considering when the extra information
|
|
1174
1564
|
# is needed for the update graph building, or only looked up in downstream tasks (metadata).
|
|
1175
|
-
request_parameters = request.get("Parameters",
|
|
1565
|
+
request_parameters = request.get("Parameters", [])
|
|
1176
1566
|
# TODO: handle parameter defaults and resolution
|
|
1177
|
-
after_parameters
|
|
1178
|
-
|
|
1179
|
-
for parameter in request_parameters
|
|
1180
|
-
}
|
|
1567
|
+
after_parameters = self._extract_after_parameters(request_parameters, before_parameters)
|
|
1568
|
+
|
|
1181
1569
|
before_template = stack.template
|
|
1182
1570
|
after_template = structured_template
|
|
1183
1571
|
|
|
@@ -1189,6 +1577,7 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
1189
1577
|
change_set = ChangeSet(
|
|
1190
1578
|
stack,
|
|
1191
1579
|
{"ChangeSetName": f"cs-{stack_name}-create", "ChangeSetType": ChangeSetType.CREATE},
|
|
1580
|
+
template_body=template_body,
|
|
1192
1581
|
template=after_template,
|
|
1193
1582
|
)
|
|
1194
1583
|
self._setup_change_set_model(
|
|
@@ -1203,7 +1592,7 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
1203
1592
|
# TODO: some changes are only detectable at runtime; consider using
|
|
1204
1593
|
# the ChangeSetModelDescriber, or a new custom visitors, to
|
|
1205
1594
|
# pick-up on runtime changes.
|
|
1206
|
-
if change_set.
|
|
1595
|
+
if not change_set.has_changes():
|
|
1207
1596
|
raise ValidationError("No updates are to be performed.")
|
|
1208
1597
|
|
|
1209
1598
|
stack.set_stack_status(StackStatus.UPDATE_IN_PROGRESS)
|
|
@@ -1214,20 +1603,51 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
1214
1603
|
result = change_set_executor.execute()
|
|
1215
1604
|
stack.set_stack_status(StackStatus.UPDATE_COMPLETE)
|
|
1216
1605
|
stack.resolved_resources = result.resources
|
|
1217
|
-
stack.resolved_parameters = result.parameters
|
|
1218
1606
|
stack.resolved_outputs = result.outputs
|
|
1219
1607
|
# if the deployment succeeded, update the stack's template representation to that
|
|
1220
1608
|
# which was just deployed
|
|
1221
1609
|
stack.template = change_set.template
|
|
1610
|
+
stack.template_body = change_set.template_body
|
|
1611
|
+
stack.resolved_parameters = change_set.resolved_parameters
|
|
1612
|
+
stack.resolved_exports = {}
|
|
1613
|
+
for output in result.outputs:
|
|
1614
|
+
if export_name := output.get("ExportName"):
|
|
1615
|
+
stack.resolved_exports[export_name] = output["OutputValue"]
|
|
1222
1616
|
except Exception as e:
|
|
1223
|
-
LOG.error(
|
|
1617
|
+
LOG.error(
|
|
1618
|
+
"Update Stack failed: %s",
|
|
1619
|
+
e,
|
|
1620
|
+
exc_info=LOG.isEnabledFor(logging.WARNING) and config.CFN_VERBOSE_ERRORS,
|
|
1621
|
+
)
|
|
1224
1622
|
stack.set_stack_status(StackStatus.UPDATE_FAILED)
|
|
1225
1623
|
|
|
1226
1624
|
start_worker_thread(_run)
|
|
1227
1625
|
|
|
1228
|
-
# TODO: stack id
|
|
1229
1626
|
return UpdateStackOutput(StackId=stack.stack_id)
|
|
1230
1627
|
|
|
1628
|
+
@staticmethod
|
|
1629
|
+
def _extract_after_parameters(
|
|
1630
|
+
request_parameters, before_parameters: dict[str, str] | None = None
|
|
1631
|
+
) -> dict[str, str]:
|
|
1632
|
+
before_parameters = before_parameters or {}
|
|
1633
|
+
after_parameters = {}
|
|
1634
|
+
for parameter in request_parameters:
|
|
1635
|
+
key = parameter["ParameterKey"]
|
|
1636
|
+
if parameter.get("UsePreviousValue", False):
|
|
1637
|
+
# todo: what if the parameter does not exist in the before parameters
|
|
1638
|
+
before = before_parameters[key]
|
|
1639
|
+
after_parameters[key] = (
|
|
1640
|
+
before.get("resolved_value")
|
|
1641
|
+
or before.get("given_value")
|
|
1642
|
+
or before.get("default_value")
|
|
1643
|
+
)
|
|
1644
|
+
continue
|
|
1645
|
+
|
|
1646
|
+
if "ParameterValue" in parameter:
|
|
1647
|
+
after_parameters[key] = parameter["ParameterValue"]
|
|
1648
|
+
continue
|
|
1649
|
+
return after_parameters
|
|
1650
|
+
|
|
1231
1651
|
@handler("DeleteStack")
|
|
1232
1652
|
def delete_stack(
|
|
1233
1653
|
self,
|
|
@@ -1249,40 +1669,45 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
1249
1669
|
# created, but never executed
|
|
1250
1670
|
if stack.status == StackStatus.REVIEW_IN_PROGRESS and not stack.resolved_resources:
|
|
1251
1671
|
stack.set_stack_status(StackStatus.DELETE_COMPLETE)
|
|
1252
|
-
stack.deletion_time = datetime.now(tz=
|
|
1672
|
+
stack.deletion_time = datetime.now(tz=UTC)
|
|
1253
1673
|
return
|
|
1254
1674
|
|
|
1255
|
-
|
|
1256
|
-
if stack.change_set_id:
|
|
1257
|
-
if previous_change_set := find_change_set_v2(state, stack.change_set_id):
|
|
1258
|
-
previous_update_model = previous_change_set.update_model
|
|
1675
|
+
stack.set_stack_status(StackStatus.DELETE_IN_PROGRESS)
|
|
1259
1676
|
|
|
1260
1677
|
# create a dummy change set
|
|
1261
|
-
change_set = ChangeSet(
|
|
1678
|
+
change_set = ChangeSet(
|
|
1679
|
+
stack, {"ChangeSetName": f"delete-stack_{stack.stack_name}"}, template_body=""
|
|
1680
|
+
) # noqa
|
|
1262
1681
|
self._setup_change_set_model(
|
|
1263
1682
|
change_set=change_set,
|
|
1264
|
-
before_template=stack.
|
|
1683
|
+
before_template=stack.processed_template,
|
|
1265
1684
|
after_template=None,
|
|
1266
1685
|
before_parameters=stack.resolved_parameters,
|
|
1267
1686
|
after_parameters=None,
|
|
1268
|
-
previous_update_model=previous_update_model,
|
|
1269
1687
|
)
|
|
1270
1688
|
|
|
1271
1689
|
change_set_executor = ChangeSetModelExecutor(change_set)
|
|
1272
1690
|
|
|
1273
1691
|
def _run(*args):
|
|
1274
1692
|
try:
|
|
1275
|
-
stack.set_stack_status(StackStatus.DELETE_IN_PROGRESS)
|
|
1276
1693
|
change_set_executor.execute()
|
|
1277
1694
|
stack.set_stack_status(StackStatus.DELETE_COMPLETE)
|
|
1278
|
-
stack.deletion_time = datetime.now(tz=
|
|
1695
|
+
stack.deletion_time = datetime.now(tz=UTC)
|
|
1279
1696
|
except Exception as e:
|
|
1280
1697
|
LOG.warning(
|
|
1281
1698
|
"Failed to delete stack '%s': %s",
|
|
1282
1699
|
stack.stack_name,
|
|
1283
1700
|
e,
|
|
1284
|
-
exc_info=LOG.isEnabledFor(logging.DEBUG),
|
|
1701
|
+
exc_info=LOG.isEnabledFor(logging.DEBUG) and config.CFN_VERBOSE_ERRORS,
|
|
1285
1702
|
)
|
|
1286
1703
|
stack.set_stack_status(StackStatus.DELETE_FAILED)
|
|
1287
1704
|
|
|
1288
1705
|
start_worker_thread(_run)
|
|
1706
|
+
return ExecuteChangeSetOutput()
|
|
1707
|
+
|
|
1708
|
+
@handler("ListExports")
|
|
1709
|
+
def list_exports(
|
|
1710
|
+
self, context: RequestContext, next_token: NextToken = None, **kwargs
|
|
1711
|
+
) -> ListExportsOutput:
|
|
1712
|
+
store = get_cloudformation_store(account_id=context.account_id, region_name=context.region)
|
|
1713
|
+
return ListExportsOutput(Exports=store.exports_v2.values())
|