localstack-core 4.6.1.dev60__py3-none-any.whl → 4.10.1.dev12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- localstack/aws/api/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 +1545 -66
- 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 +36 -23
- 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 +44 -30
- localstack/constants.py +4 -29
- localstack/deprecations.py +5 -5
- localstack/dev/kubernetes/__main__.py +130 -7
- 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 +289 -136
- 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 +183 -0
- 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 +11 -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 +33 -22
- localstack/services/cloudformation/v2/entities.py +103 -88
- localstack/services/cloudformation/v2/provider.py +872 -131
- 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 +159 -18
- 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 +157 -18
- 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 +62 -26
- 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 +39 -22
- 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 +7 -5
- 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 +36 -23
- 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 -49
- 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 +119 -0
- localstack/utils/catalog/common.py +58 -0
- localstack/utils/catalog/plugins.py +28 -0
- localstack/utils/cloudwatch/cloudwatch_util.py +5 -5
- localstack/utils/collections.py +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.dev60.dist-info → localstack_core-4.10.1.dev12.dist-info}/METADATA +18 -13
- {localstack_core-4.6.1.dev60.dist-info → localstack_core-4.10.1.dev12.dist-info}/RECORD +663 -655
- {localstack_core-4.6.1.dev60.dist-info → localstack_core-4.10.1.dev12.dist-info}/entry_points.txt +8 -4
- localstack_core-4.10.1.dev12.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.dev60.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.dev60.data → localstack_core-4.10.1.dev12.data}/scripts/localstack +0 -0
- {localstack_core-4.6.1.dev60.data → localstack_core-4.10.1.dev12.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.6.1.dev60.data → localstack_core-4.10.1.dev12.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.6.1.dev60.dist-info → localstack_core-4.10.1.dev12.dist-info}/WHEEL +0 -0
- {localstack_core-4.6.1.dev60.dist-info → localstack_core-4.10.1.dev12.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.6.1.dev60.dist-info → localstack_core-4.10.1.dev12.dist-info}/top_level.txt +0 -0
|
@@ -1,27 +1,41 @@
|
|
|
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,
|
|
13
|
+
CallAs,
|
|
10
14
|
Changes,
|
|
11
15
|
ChangeSetNameOrId,
|
|
12
16
|
ChangeSetNotFoundException,
|
|
13
17
|
ChangeSetStatus,
|
|
18
|
+
ChangeSetSummary,
|
|
14
19
|
ChangeSetType,
|
|
15
20
|
ClientRequestToken,
|
|
16
21
|
CreateChangeSetInput,
|
|
17
22
|
CreateChangeSetOutput,
|
|
18
23
|
CreateStackInput,
|
|
24
|
+
CreateStackInstancesInput,
|
|
25
|
+
CreateStackInstancesOutput,
|
|
19
26
|
CreateStackOutput,
|
|
27
|
+
CreateStackSetInput,
|
|
28
|
+
CreateStackSetOutput,
|
|
20
29
|
DeleteChangeSetOutput,
|
|
30
|
+
DeleteStackInstancesInput,
|
|
31
|
+
DeleteStackInstancesOutput,
|
|
32
|
+
DeleteStackSetOutput,
|
|
21
33
|
DeletionMode,
|
|
22
34
|
DescribeChangeSetOutput,
|
|
23
35
|
DescribeStackEventsOutput,
|
|
36
|
+
DescribeStackResourceOutput,
|
|
24
37
|
DescribeStackResourcesOutput,
|
|
38
|
+
DescribeStackSetOperationOutput,
|
|
25
39
|
DescribeStacksOutput,
|
|
26
40
|
DisableRollback,
|
|
27
41
|
EnableTerminationProtection,
|
|
@@ -33,17 +47,30 @@ from localstack.aws.api.cloudformation import (
|
|
|
33
47
|
IncludePropertyValues,
|
|
34
48
|
InsufficientCapabilitiesException,
|
|
35
49
|
InvalidChangeSetStatusException,
|
|
50
|
+
ListChangeSetsOutput,
|
|
51
|
+
ListExportsOutput,
|
|
52
|
+
ListStackResourcesOutput,
|
|
36
53
|
ListStacksOutput,
|
|
37
54
|
LogicalResourceId,
|
|
38
55
|
NextToken,
|
|
39
56
|
Parameter,
|
|
40
57
|
PhysicalResourceId,
|
|
58
|
+
ResourceStatus,
|
|
41
59
|
RetainExceptOnCreate,
|
|
42
60
|
RetainResources,
|
|
43
61
|
RoleARN,
|
|
44
62
|
RollbackConfiguration,
|
|
63
|
+
StackDriftInformation,
|
|
64
|
+
StackDriftStatus,
|
|
45
65
|
StackName,
|
|
46
66
|
StackNameOrId,
|
|
67
|
+
StackResourceDetail,
|
|
68
|
+
StackResourceSummary,
|
|
69
|
+
StackSetName,
|
|
70
|
+
StackSetNotFoundException,
|
|
71
|
+
StackSetOperation,
|
|
72
|
+
StackSetOperationAction,
|
|
73
|
+
StackSetOperationStatus,
|
|
47
74
|
StackStatus,
|
|
48
75
|
StackStatusFilter,
|
|
49
76
|
TemplateStage,
|
|
@@ -51,8 +78,14 @@ from localstack.aws.api.cloudformation import (
|
|
|
51
78
|
UpdateStackOutput,
|
|
52
79
|
UpdateTerminationProtectionOutput,
|
|
53
80
|
)
|
|
81
|
+
from localstack.aws.api.cloudformation import (
|
|
82
|
+
Stack as ApiStack,
|
|
83
|
+
)
|
|
84
|
+
from localstack.aws.connect import connect_to
|
|
54
85
|
from localstack.services.cloudformation import api_utils
|
|
55
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
|
|
56
89
|
from localstack.services.cloudformation.engine.v2.change_set_model import (
|
|
57
90
|
ChangeSetModel,
|
|
58
91
|
ChangeType,
|
|
@@ -67,22 +100,39 @@ from localstack.services.cloudformation.engine.v2.change_set_model_executor impo
|
|
|
67
100
|
from localstack.services.cloudformation.engine.v2.change_set_model_transform import (
|
|
68
101
|
ChangeSetModelTransform,
|
|
69
102
|
)
|
|
103
|
+
from localstack.services.cloudformation.engine.v2.change_set_model_validator import (
|
|
104
|
+
ChangeSetModelValidator,
|
|
105
|
+
)
|
|
70
106
|
from localstack.services.cloudformation.engine.validations import ValidationError
|
|
71
107
|
from localstack.services.cloudformation.provider import (
|
|
72
108
|
ARN_CHANGESET_REGEX,
|
|
73
109
|
ARN_STACK_REGEX,
|
|
110
|
+
ARN_STACK_SET_REGEX,
|
|
74
111
|
CloudformationProvider,
|
|
75
112
|
)
|
|
76
113
|
from localstack.services.cloudformation.stores import (
|
|
77
114
|
CloudFormationStore,
|
|
78
115
|
get_cloudformation_store,
|
|
79
116
|
)
|
|
80
|
-
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
|
|
81
125
|
from localstack.utils.collections import select_attributes
|
|
126
|
+
from localstack.utils.numbers import is_number
|
|
127
|
+
from localstack.utils.strings import short_uid
|
|
82
128
|
from localstack.utils.threads import start_worker_thread
|
|
83
129
|
|
|
84
130
|
LOG = logging.getLogger(__name__)
|
|
85
131
|
|
|
132
|
+
SSM_PARAMETER_TYPE_RE = re.compile(
|
|
133
|
+
r"^AWS::SSM::Parameter::Value<(?P<listtype>List<)?(?P<innertype>[^>]+)>?>$"
|
|
134
|
+
)
|
|
135
|
+
|
|
86
136
|
|
|
87
137
|
def is_stack_arn(stack_name_or_id: str) -> bool:
|
|
88
138
|
return ARN_STACK_REGEX.match(stack_name_or_id) is not None
|
|
@@ -92,12 +142,24 @@ def is_changeset_arn(change_set_name_or_id: str) -> bool:
|
|
|
92
142
|
return ARN_CHANGESET_REGEX.match(change_set_name_or_id) is not None
|
|
93
143
|
|
|
94
144
|
|
|
145
|
+
def is_stack_set_arn(stack_set_name_or_id: str) -> bool:
|
|
146
|
+
return ARN_STACK_SET_REGEX.match(stack_set_name_or_id) is not None
|
|
147
|
+
|
|
148
|
+
|
|
95
149
|
class StackNotFoundError(ValidationError):
|
|
96
|
-
def __init__(self, stack_name_or_id: str):
|
|
97
|
-
if
|
|
98
|
-
super().__init__(
|
|
150
|
+
def __init__(self, stack_name_or_id: str, message_override: str | None = None):
|
|
151
|
+
if message_override:
|
|
152
|
+
super().__init__(message_override)
|
|
99
153
|
else:
|
|
100
|
-
|
|
154
|
+
if is_stack_arn(stack_name_or_id):
|
|
155
|
+
super().__init__(f"Stack with id {stack_name_or_id} does not exist")
|
|
156
|
+
else:
|
|
157
|
+
super().__init__(f"Stack [{stack_name_or_id}] does not exist")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class StackSetNotFoundError(StackSetNotFoundException):
|
|
161
|
+
def __init__(self, stack_set_name: str):
|
|
162
|
+
super().__init__(f"StackSet {stack_set_name} not found")
|
|
101
163
|
|
|
102
164
|
|
|
103
165
|
def find_stack_v2(state: CloudFormationStore, stack_name: str | None) -> Stack | None:
|
|
@@ -123,7 +185,7 @@ def find_change_set_v2(
|
|
|
123
185
|
state: CloudFormationStore, change_set_name: str, stack_name: str | None = None
|
|
124
186
|
) -> ChangeSet | None:
|
|
125
187
|
if is_changeset_arn(change_set_name):
|
|
126
|
-
return state.change_sets
|
|
188
|
+
return state.change_sets.get(change_set_name)
|
|
127
189
|
else:
|
|
128
190
|
if stack_name is not None:
|
|
129
191
|
stack = find_stack_v2(state, stack_name)
|
|
@@ -135,25 +197,166 @@ def find_change_set_v2(
|
|
|
135
197
|
if change_set_candidate.change_set_name == change_set_name:
|
|
136
198
|
return change_set_candidate
|
|
137
199
|
else:
|
|
138
|
-
raise
|
|
200
|
+
raise ValidationError(
|
|
201
|
+
"StackName must be specified if ChangeSetName is not specified as an ARN."
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def find_stack_set_v2(state: CloudFormationStore, stack_set_name: str) -> StackSet | None:
|
|
206
|
+
if is_stack_set_arn(stack_set_name):
|
|
207
|
+
return state.stack_sets.get(stack_set_name)
|
|
139
208
|
|
|
209
|
+
for stack_set in state.stack_sets_v2.values():
|
|
210
|
+
if stack_set.stack_set_name == stack_set_name:
|
|
211
|
+
return stack_set
|
|
212
|
+
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def find_stack_instance(stack_set: StackSet, account: str, region: str) -> StackInstance | None:
|
|
217
|
+
for instance in stack_set.stack_instances:
|
|
218
|
+
if instance.account_id == account and instance.region_name == region:
|
|
219
|
+
return instance
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
|
|
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
|
+
)
|
|
140
244
|
|
|
141
|
-
class CloudformationProviderV2(CloudformationProvider):
|
|
142
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
|
|
143
333
|
def _setup_change_set_model(
|
|
334
|
+
cls,
|
|
144
335
|
change_set: ChangeSet,
|
|
145
|
-
before_template:
|
|
146
|
-
after_template:
|
|
147
|
-
before_parameters:
|
|
148
|
-
after_parameters:
|
|
149
|
-
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,
|
|
150
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
|
+
|
|
151
354
|
# Create and preprocess the update graph for this template update.
|
|
152
355
|
change_set_model = ChangeSetModel(
|
|
153
356
|
before_template=before_template,
|
|
154
357
|
after_template=after_template,
|
|
155
358
|
before_parameters=before_parameters,
|
|
156
|
-
after_parameters=
|
|
359
|
+
after_parameters=resolved_parameters,
|
|
157
360
|
)
|
|
158
361
|
raw_update_model: UpdateModel = change_set_model.get_update_model()
|
|
159
362
|
# If there exists an update model which operated in the 'before' version of this change set,
|
|
@@ -168,20 +371,29 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
168
371
|
change_set_model_transform = ChangeSetModelTransform(
|
|
169
372
|
change_set=change_set,
|
|
170
373
|
before_parameters=before_parameters,
|
|
171
|
-
after_parameters=
|
|
374
|
+
after_parameters=resolved_parameters,
|
|
172
375
|
before_template=before_template,
|
|
173
376
|
after_template=after_template,
|
|
174
377
|
)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
|
178
390
|
|
|
179
391
|
# Remodel the update graph after the applying the global transforms.
|
|
180
392
|
change_set_model = ChangeSetModel(
|
|
181
393
|
before_template=transformed_before_template,
|
|
182
394
|
after_template=transformed_after_template,
|
|
183
395
|
before_parameters=before_parameters,
|
|
184
|
-
after_parameters=
|
|
396
|
+
after_parameters=resolved_parameters,
|
|
185
397
|
)
|
|
186
398
|
update_model = change_set_model.get_update_model()
|
|
187
399
|
# Bring the cache for the previous operations forward in the update graph for this version
|
|
@@ -191,15 +403,26 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
191
403
|
update_model.before_runtime_cache.update(raw_update_model.before_runtime_cache)
|
|
192
404
|
update_model.after_runtime_cache.update(raw_update_model.after_runtime_cache)
|
|
193
405
|
change_set.set_update_model(update_model)
|
|
194
|
-
|
|
406
|
+
|
|
407
|
+
# perform validations
|
|
408
|
+
validator = ChangeSetModelValidator(
|
|
409
|
+
change_set=change_set,
|
|
410
|
+
)
|
|
411
|
+
validator.validate()
|
|
412
|
+
|
|
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
|
|
195
419
|
|
|
196
420
|
@handler("CreateChangeSet", expand=False)
|
|
197
421
|
def create_change_set(
|
|
198
422
|
self, context: RequestContext, request: CreateChangeSetInput
|
|
199
423
|
) -> CreateChangeSetOutput:
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
except KeyError:
|
|
424
|
+
stack_name = request.get("StackName")
|
|
425
|
+
if not stack_name:
|
|
203
426
|
# TODO: proper exception
|
|
204
427
|
raise ValidationError("StackName must be specified")
|
|
205
428
|
try:
|
|
@@ -229,12 +452,19 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
229
452
|
template_body = api_utils.extract_template_body(request)
|
|
230
453
|
structured_template = template_preparer.parse_template(template_body)
|
|
231
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
|
+
|
|
232
461
|
# this is intentionally not in a util yet. Let's first see how the different operations deal with these before generalizing
|
|
233
462
|
# handle ARN stack_name here (not valid for initial CREATE, since stack doesn't exist yet)
|
|
234
463
|
if is_stack_arn(stack_name):
|
|
235
464
|
stack = state.stacks_v2.get(stack_name)
|
|
236
465
|
if not stack:
|
|
237
466
|
raise ValidationError(f"Stack '{stack_name}' does not exist.")
|
|
467
|
+
stack.capabilities = request.get("Capabilities") or []
|
|
238
468
|
else:
|
|
239
469
|
# stack name specified, so fetch the stack by name
|
|
240
470
|
stack_candidates: list[Stack] = [
|
|
@@ -248,8 +478,6 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
248
478
|
account_id=context.account_id,
|
|
249
479
|
region_name=context.region,
|
|
250
480
|
request_payload=request,
|
|
251
|
-
template=structured_template,
|
|
252
|
-
template_body=template_body,
|
|
253
481
|
initial_status=StackStatus.REVIEW_IN_PROGRESS,
|
|
254
482
|
)
|
|
255
483
|
state.stacks_v2[stack.stack_id] = stack
|
|
@@ -257,6 +485,8 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
257
485
|
if not active_stack_candidates:
|
|
258
486
|
raise ValidationError(f"Stack '{stack_name}' does not exist.")
|
|
259
487
|
stack = active_stack_candidates[0]
|
|
488
|
+
# propagate capabilities from create change set request
|
|
489
|
+
stack.capabilities = request.get("Capabilities") or []
|
|
260
490
|
|
|
261
491
|
# TODO: test if rollback status is allowed as well
|
|
262
492
|
if (
|
|
@@ -267,6 +497,14 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
267
497
|
f"Stack [{stack_name}] already exists and cannot be created again with the changeSet [{change_set_name}]."
|
|
268
498
|
)
|
|
269
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
|
+
|
|
270
508
|
before_parameters: dict[str, Parameter] | None = None
|
|
271
509
|
match change_set_type:
|
|
272
510
|
case ChangeSetType.UPDATE:
|
|
@@ -291,12 +529,9 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
291
529
|
# The options might be reduce to using the current style, or passing the extra information
|
|
292
530
|
# as a metadata object. The choice should be made considering when the extra information
|
|
293
531
|
# is needed for the update graph building, or only looked up in downstream tasks (metadata).
|
|
294
|
-
request_parameters = request.get("Parameters",
|
|
532
|
+
request_parameters = request.get("Parameters", [])
|
|
295
533
|
# TODO: handle parameter defaults and resolution
|
|
296
|
-
after_parameters
|
|
297
|
-
parameter["ParameterKey"]: parameter["ParameterValue"]
|
|
298
|
-
for parameter in request_parameters
|
|
299
|
-
}
|
|
534
|
+
after_parameters = self._extract_after_parameters(request_parameters, before_parameters)
|
|
300
535
|
|
|
301
536
|
# TODO: update this logic to always pass the clean template object if one exists. The
|
|
302
537
|
# current issue with relaying on stack.template_original is that this appears to have
|
|
@@ -316,7 +551,12 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
316
551
|
pass
|
|
317
552
|
|
|
318
553
|
# create change set for the stack and apply changes
|
|
319
|
-
change_set = ChangeSet(
|
|
554
|
+
change_set = ChangeSet(
|
|
555
|
+
stack,
|
|
556
|
+
request,
|
|
557
|
+
template=after_template,
|
|
558
|
+
template_body=template_body,
|
|
559
|
+
)
|
|
320
560
|
self._setup_change_set_model(
|
|
321
561
|
change_set=change_set,
|
|
322
562
|
before_template=before_template,
|
|
@@ -325,24 +565,21 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
325
565
|
after_parameters=after_parameters,
|
|
326
566
|
previous_update_model=previous_update_model,
|
|
327
567
|
)
|
|
328
|
-
|
|
329
|
-
# TODO: handle the empty change set case
|
|
330
|
-
if not change_set.has_changes():
|
|
331
|
-
change_set.set_change_set_status(ChangeSetStatus.FAILED)
|
|
568
|
+
if change_set.status == ChangeSetStatus.FAILED:
|
|
332
569
|
change_set.set_execution_status(ExecutionStatus.UNAVAILABLE)
|
|
333
|
-
change_set.status_reason = "The submitted information didn't contain changes. Submit different information to create a change set."
|
|
334
570
|
else:
|
|
335
|
-
if
|
|
336
|
-
|
|
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."
|
|
337
575
|
else:
|
|
338
|
-
stack.
|
|
576
|
+
if stack.status not in [StackStatus.CREATE_COMPLETE, StackStatus.UPDATE_COMPLETE]:
|
|
577
|
+
stack.set_stack_status(StackStatus.REVIEW_IN_PROGRESS, "User Initiated")
|
|
339
578
|
|
|
340
|
-
|
|
579
|
+
change_set.set_change_set_status(ChangeSetStatus.CREATE_COMPLETE)
|
|
341
580
|
|
|
342
|
-
stack.
|
|
343
|
-
stack.change_set_ids.append(change_set.change_set_id)
|
|
581
|
+
stack.change_set_ids.add(change_set.change_set_id)
|
|
344
582
|
state.change_sets[change_set.change_set_id] = change_set
|
|
345
|
-
|
|
346
583
|
return CreateChangeSetOutput(StackId=stack.stack_id, Id=change_set.change_set_id)
|
|
347
584
|
|
|
348
585
|
@handler("ExecuteChangeSet")
|
|
@@ -377,6 +614,8 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
377
614
|
raise RuntimeError("Programming error: no update graph found for change set")
|
|
378
615
|
|
|
379
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
|
|
380
619
|
change_set.stack.set_stack_status(
|
|
381
620
|
StackStatus.UPDATE_IN_PROGRESS
|
|
382
621
|
if change_set.change_set_type == ChangeSetType.UPDATE
|
|
@@ -388,37 +627,104 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
388
627
|
)
|
|
389
628
|
|
|
390
629
|
def _run(*args):
|
|
391
|
-
|
|
392
|
-
|
|
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:
|
|
393
641
|
new_stack_status = StackStatus.UPDATE_COMPLETE
|
|
394
642
|
if change_set.change_set_type == ChangeSetType.CREATE:
|
|
395
643
|
new_stack_status = StackStatus.CREATE_COMPLETE
|
|
396
644
|
change_set.stack.set_stack_status(new_stack_status)
|
|
397
645
|
change_set.set_execution_status(ExecutionStatus.EXECUTE_COMPLETE)
|
|
398
|
-
change_set.stack.resolved_resources = result.resources
|
|
399
|
-
change_set.stack.resolved_parameters = result.parameters
|
|
400
646
|
change_set.stack.resolved_outputs = result.outputs
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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:
|
|
405
655
|
LOG.error(
|
|
406
|
-
"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,
|
|
407
659
|
)
|
|
408
|
-
|
|
409
|
-
if change_set.change_set_type == ChangeSetType.CREATE:
|
|
410
|
-
new_stack_status = StackStatus.CREATE_FAILED
|
|
411
|
-
|
|
412
|
-
change_set.stack.set_stack_status(new_stack_status)
|
|
660
|
+
# stack status is taken care of in the executor
|
|
413
661
|
change_set.set_execution_status(ExecutionStatus.EXECUTE_FAILED)
|
|
662
|
+
change_set.stack.deletion_time = datetime.now(tz=UTC)
|
|
414
663
|
|
|
415
664
|
start_worker_thread(_run)
|
|
416
665
|
|
|
417
666
|
return ExecuteChangeSetOutput()
|
|
418
667
|
|
|
419
|
-
|
|
420
|
-
|
|
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,
|
|
421
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
|
+
|
|
422
728
|
# TODO: The ChangeSetModelDescriber currently matches AWS behavior by listing
|
|
423
729
|
# resource changes in the order they appear in the template. However, when
|
|
424
730
|
# a resource change is triggered indirectly (e.g., via Ref or GetAtt), the
|
|
@@ -440,38 +746,56 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
440
746
|
StackId=change_set.stack.stack_id,
|
|
441
747
|
StackName=change_set.stack.stack_name,
|
|
442
748
|
CreationTime=change_set.creation_time,
|
|
443
|
-
Parameters=[
|
|
444
|
-
# TODO: add masking support.
|
|
445
|
-
Parameter(ParameterKey=key, ParameterValue=value)
|
|
446
|
-
for (key, value) in change_set.stack.resolved_parameters.items()
|
|
447
|
-
],
|
|
448
749
|
Changes=changes,
|
|
449
750
|
Capabilities=change_set.stack.capabilities,
|
|
450
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,
|
|
451
757
|
)
|
|
758
|
+
if change_set.resolved_parameters:
|
|
759
|
+
result["Parameters"] = self._render_resolved_parameters(change_set.resolved_parameters)
|
|
452
760
|
return result
|
|
453
761
|
|
|
454
|
-
@handler("
|
|
455
|
-
def
|
|
762
|
+
@handler("ListChangeSets")
|
|
763
|
+
def list_change_sets(
|
|
456
764
|
self,
|
|
457
765
|
context: RequestContext,
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
next_token: NextToken | None = None,
|
|
461
|
-
include_property_values: IncludePropertyValues | None = None,
|
|
766
|
+
stack_name: StackNameOrId,
|
|
767
|
+
next_token: NextToken = None,
|
|
462
768
|
**kwargs,
|
|
463
|
-
) ->
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
+
)
|
|
468
797
|
|
|
469
|
-
|
|
470
|
-
raise ChangeSetNotFoundException(f"ChangeSet [{change_set_name}] does not exist")
|
|
471
|
-
result = self._describe_change_set(
|
|
472
|
-
change_set=change_set, include_property_values=include_property_values or False
|
|
473
|
-
)
|
|
474
|
-
return result
|
|
798
|
+
return ListChangeSetsOutput(Summaries=summaries)
|
|
475
799
|
|
|
476
800
|
@handler("DeleteChangeSet")
|
|
477
801
|
def delete_change_set(
|
|
@@ -482,21 +806,26 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
482
806
|
**kwargs,
|
|
483
807
|
) -> DeleteChangeSetOutput:
|
|
484
808
|
state = get_cloudformation_store(context.account_id, context.region)
|
|
485
|
-
|
|
486
|
-
if is_changeset_arn(change_set_name):
|
|
487
|
-
change_set = state.change_sets.get(change_set_name)
|
|
488
|
-
elif not is_changeset_arn(change_set_name) and stack_name:
|
|
489
|
-
change_set = find_change_set_v2(state, change_set_name, stack_name)
|
|
490
|
-
else:
|
|
491
|
-
raise ValidationError(
|
|
492
|
-
"StackName must be specified if ChangeSetName is not specified as an ARN."
|
|
493
|
-
)
|
|
494
|
-
|
|
809
|
+
change_set = find_change_set_v2(state, change_set_name, stack_name)
|
|
495
810
|
if not change_set:
|
|
496
811
|
return DeleteChangeSetOutput()
|
|
497
812
|
|
|
498
|
-
|
|
499
|
-
|
|
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
|
+
)
|
|
500
829
|
|
|
501
830
|
return DeleteChangeSetOutput()
|
|
502
831
|
|
|
@@ -509,6 +838,26 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
509
838
|
raise ValidationError("StackName must be specified")
|
|
510
839
|
|
|
511
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
|
+
|
|
512
861
|
# TODO: copied from create_change_set, consider unifying
|
|
513
862
|
template_body = request.get("TemplateBody")
|
|
514
863
|
# s3 or secretsmanager url
|
|
@@ -528,6 +877,12 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
528
877
|
template_body = api_utils.extract_template_body(request)
|
|
529
878
|
structured_template = template_preparer.parse_template(template_body)
|
|
530
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
|
+
|
|
531
886
|
if "CAPABILITY_AUTO_EXPAND" not in request.get("Capabilities", []) and (
|
|
532
887
|
"Transform" in structured_template.keys() or "Fn::Transform" in template_body
|
|
533
888
|
):
|
|
@@ -539,8 +894,7 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
539
894
|
account_id=context.account_id,
|
|
540
895
|
region_name=context.region,
|
|
541
896
|
request_payload=request,
|
|
542
|
-
|
|
543
|
-
template_body=template_body,
|
|
897
|
+
tags=request.get("Tags"),
|
|
544
898
|
)
|
|
545
899
|
# TODO: what is the correct initial status?
|
|
546
900
|
state.stacks_v2[stack.stack_id] = stack
|
|
@@ -549,12 +903,9 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
549
903
|
# The options might be reduce to using the current style, or passing the extra information
|
|
550
904
|
# as a metadata object. The choice should be made considering when the extra information
|
|
551
905
|
# is needed for the update graph building, or only looked up in downstream tasks (metadata).
|
|
552
|
-
request_parameters = request.get("Parameters",
|
|
906
|
+
request_parameters = request.get("Parameters", [])
|
|
553
907
|
# TODO: handle parameter defaults and resolution
|
|
554
|
-
after_parameters
|
|
555
|
-
parameter["ParameterKey"]: parameter["ParameterValue"]
|
|
556
|
-
for parameter in request_parameters
|
|
557
|
-
}
|
|
908
|
+
after_parameters = self._extract_after_parameters(request_parameters)
|
|
558
909
|
after_template = structured_template
|
|
559
910
|
|
|
560
911
|
# Create internal change set to execute
|
|
@@ -562,6 +913,7 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
562
913
|
stack,
|
|
563
914
|
{"ChangeSetName": f"cs-{stack_name}-create", "ChangeSetType": ChangeSetType.CREATE},
|
|
564
915
|
template=after_template,
|
|
916
|
+
template_body=template_body,
|
|
565
917
|
)
|
|
566
918
|
self._setup_change_set_model(
|
|
567
919
|
change_set=change_set,
|
|
@@ -571,6 +923,10 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
571
923
|
after_parameters=after_parameters,
|
|
572
924
|
previous_update_model=None,
|
|
573
925
|
)
|
|
926
|
+
if change_set.status == ChangeSetStatus.FAILED:
|
|
927
|
+
return CreateStackOutput(StackId=stack.stack_id)
|
|
928
|
+
|
|
929
|
+
stack.processed_template = change_set.processed_template
|
|
574
930
|
|
|
575
931
|
# deployment process
|
|
576
932
|
stack.set_stack_status(StackStatus.CREATE_IN_PROGRESS)
|
|
@@ -579,16 +935,31 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
579
935
|
def _run(*args):
|
|
580
936
|
try:
|
|
581
937
|
result = change_set_executor.execute()
|
|
582
|
-
stack.set_stack_status(StackStatus.CREATE_COMPLETE)
|
|
583
938
|
stack.resolved_resources = result.resources
|
|
584
|
-
stack.resolved_parameters = result.parameters
|
|
585
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
|
+
|
|
586
948
|
# if the deployment succeeded, update the stack's template representation to that
|
|
587
949
|
# which was just deployed
|
|
588
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"]
|
|
589
958
|
except Exception as e:
|
|
590
959
|
LOG.error(
|
|
591
|
-
"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,
|
|
592
963
|
)
|
|
593
964
|
stack.set_stack_status(StackStatus.CREATE_FAILED)
|
|
594
965
|
|
|
@@ -596,6 +967,16 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
596
967
|
|
|
597
968
|
return CreateStackOutput(StackId=stack.stack_id)
|
|
598
969
|
|
|
970
|
+
@handler("CreateStackSet", expand=False)
|
|
971
|
+
def create_stack_set(
|
|
972
|
+
self, context: RequestContext, request: CreateStackSetInput
|
|
973
|
+
) -> CreateStackSetOutput:
|
|
974
|
+
state = get_cloudformation_store(context.account_id, context.region)
|
|
975
|
+
stack_set = StackSet(context.account_id, context.region, request)
|
|
976
|
+
state.stack_sets_v2[stack_set.stack_set_id] = stack_set
|
|
977
|
+
|
|
978
|
+
return CreateStackSetOutput(StackSetId=stack_set.stack_set_id)
|
|
979
|
+
|
|
599
980
|
@handler("DescribeStacks")
|
|
600
981
|
def describe_stacks(
|
|
601
982
|
self,
|
|
@@ -605,11 +986,56 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
605
986
|
**kwargs,
|
|
606
987
|
) -> DescribeStacksOutput:
|
|
607
988
|
state = get_cloudformation_store(context.account_id, context.region)
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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
|
|
613
1039
|
|
|
614
1040
|
@handler("ListStacks")
|
|
615
1041
|
def list_stacks(
|
|
@@ -622,7 +1048,7 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
622
1048
|
state = get_cloudformation_store(context.account_id, context.region)
|
|
623
1049
|
|
|
624
1050
|
stacks = [
|
|
625
|
-
|
|
1051
|
+
self._describe_stack(s)
|
|
626
1052
|
for s in state.stacks_v2.values()
|
|
627
1053
|
if not stack_status_filter or s.status in stack_status_filter
|
|
628
1054
|
]
|
|
@@ -643,6 +1069,69 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
643
1069
|
stacks = [select_attributes(stack, attrs) for stack in stacks]
|
|
644
1070
|
return ListStacksOutput(StackSummaries=stacks)
|
|
645
1071
|
|
|
1072
|
+
@handler("ListStackResources")
|
|
1073
|
+
def list_stack_resources(
|
|
1074
|
+
self, context: RequestContext, stack_name: StackName, next_token: NextToken = None, **kwargs
|
|
1075
|
+
) -> ListStackResourcesOutput:
|
|
1076
|
+
result = self.describe_stack_resources(context, stack_name)
|
|
1077
|
+
|
|
1078
|
+
resources = []
|
|
1079
|
+
for resource in result.get("StackResources", []):
|
|
1080
|
+
resources.append(
|
|
1081
|
+
StackResourceSummary(
|
|
1082
|
+
LogicalResourceId=resource["LogicalResourceId"],
|
|
1083
|
+
PhysicalResourceId=resource["PhysicalResourceId"],
|
|
1084
|
+
ResourceType=resource["ResourceType"],
|
|
1085
|
+
LastUpdatedTimestamp=resource["Timestamp"],
|
|
1086
|
+
ResourceStatus=resource["ResourceStatus"],
|
|
1087
|
+
ResourceStatusReason=resource.get("ResourceStatusReason"),
|
|
1088
|
+
DriftInformation=resource.get("DriftInformation"),
|
|
1089
|
+
ModuleInfo=resource.get("ModuleInfo"),
|
|
1090
|
+
)
|
|
1091
|
+
)
|
|
1092
|
+
|
|
1093
|
+
return ListStackResourcesOutput(StackResourceSummaries=resources)
|
|
1094
|
+
|
|
1095
|
+
@handler("DescribeStackResource")
|
|
1096
|
+
def describe_stack_resource(
|
|
1097
|
+
self,
|
|
1098
|
+
context: RequestContext,
|
|
1099
|
+
stack_name: StackName,
|
|
1100
|
+
logical_resource_id: LogicalResourceId,
|
|
1101
|
+
**kwargs,
|
|
1102
|
+
) -> DescribeStackResourceOutput:
|
|
1103
|
+
state = get_cloudformation_store(context.account_id, context.region)
|
|
1104
|
+
stack = find_stack_v2(state, stack_name)
|
|
1105
|
+
if not stack:
|
|
1106
|
+
raise StackNotFoundError(
|
|
1107
|
+
stack_name, message_override=f"Stack '{stack_name}' does not exist"
|
|
1108
|
+
)
|
|
1109
|
+
|
|
1110
|
+
try:
|
|
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
|
|
1118
|
+
except KeyError:
|
|
1119
|
+
raise ValidationError(
|
|
1120
|
+
f"Resource {logical_resource_id} does not exist for stack {stack_name}"
|
|
1121
|
+
)
|
|
1122
|
+
|
|
1123
|
+
resource_detail = StackResourceDetail(
|
|
1124
|
+
StackName=stack.stack_name,
|
|
1125
|
+
StackId=stack.stack_id,
|
|
1126
|
+
LogicalResourceId=logical_resource_id,
|
|
1127
|
+
PhysicalResourceId=resource["PhysicalResourceId"],
|
|
1128
|
+
ResourceType=resource["Type"],
|
|
1129
|
+
LastUpdatedTimestamp=resource["LastUpdatedTimestamp"],
|
|
1130
|
+
ResourceStatus=resource["ResourceStatus"],
|
|
1131
|
+
DriftInformation={"StackResourceDriftStatus": "NOT_CHECKED"},
|
|
1132
|
+
)
|
|
1133
|
+
return DescribeStackResourceOutput(StackResourceDetail=resource_detail)
|
|
1134
|
+
|
|
646
1135
|
@handler("DescribeStackResources")
|
|
647
1136
|
def describe_stack_resources(
|
|
648
1137
|
self,
|
|
@@ -667,6 +1156,195 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
667
1156
|
statuses.append(status)
|
|
668
1157
|
return DescribeStackResourcesOutput(StackResources=statuses)
|
|
669
1158
|
|
|
1159
|
+
@handler("CreateStackInstances", expand=False)
|
|
1160
|
+
def create_stack_instances(
|
|
1161
|
+
self,
|
|
1162
|
+
context: RequestContext,
|
|
1163
|
+
request: CreateStackInstancesInput,
|
|
1164
|
+
) -> CreateStackInstancesOutput:
|
|
1165
|
+
state = get_cloudformation_store(context.account_id, context.region)
|
|
1166
|
+
|
|
1167
|
+
stack_set_name = request["StackSetName"]
|
|
1168
|
+
stack_set = find_stack_set_v2(state, stack_set_name)
|
|
1169
|
+
if not stack_set:
|
|
1170
|
+
raise StackSetNotFoundError(stack_set_name)
|
|
1171
|
+
|
|
1172
|
+
op_id = request.get("OperationId") or short_uid()
|
|
1173
|
+
accounts = request["Accounts"]
|
|
1174
|
+
regions = request["Regions"]
|
|
1175
|
+
|
|
1176
|
+
stacks_to_await = []
|
|
1177
|
+
for account in accounts:
|
|
1178
|
+
for region in regions:
|
|
1179
|
+
# deploy new stack
|
|
1180
|
+
LOG.debug(
|
|
1181
|
+
'Deploying instance for stack set "%s" in account: %s region %s',
|
|
1182
|
+
stack_set_name,
|
|
1183
|
+
account,
|
|
1184
|
+
region,
|
|
1185
|
+
)
|
|
1186
|
+
cf_client = connect_to(aws_access_key_id=account, region_name=region).cloudformation
|
|
1187
|
+
if stack_set.template_body:
|
|
1188
|
+
kwargs = {
|
|
1189
|
+
"TemplateBody": stack_set.template_body,
|
|
1190
|
+
}
|
|
1191
|
+
elif stack_set.template_url:
|
|
1192
|
+
kwargs = {
|
|
1193
|
+
"TemplateURL": stack_set.template_url,
|
|
1194
|
+
}
|
|
1195
|
+
else:
|
|
1196
|
+
# TODO: wording
|
|
1197
|
+
raise ValueError("Neither StackSet Template URL nor TemplateBody provided")
|
|
1198
|
+
stack_name = f"sset-{stack_set_name}-{account}-{region}"
|
|
1199
|
+
|
|
1200
|
+
# skip creation of existing stacks
|
|
1201
|
+
if find_stack_v2(state, stack_name):
|
|
1202
|
+
continue
|
|
1203
|
+
|
|
1204
|
+
result = cf_client.create_stack(StackName=stack_name, **kwargs)
|
|
1205
|
+
# store stack instance
|
|
1206
|
+
stack_instance = StackInstance(
|
|
1207
|
+
account_id=account,
|
|
1208
|
+
region_name=region,
|
|
1209
|
+
stack_set_id=stack_set.stack_set_id,
|
|
1210
|
+
operation_id=op_id,
|
|
1211
|
+
stack_id=result["StackId"],
|
|
1212
|
+
)
|
|
1213
|
+
stack_set.stack_instances.append(stack_instance)
|
|
1214
|
+
|
|
1215
|
+
stacks_to_await.append((stack_name, account, region))
|
|
1216
|
+
|
|
1217
|
+
# wait for completion of stack
|
|
1218
|
+
for stack_name, account_id, region_name in stacks_to_await:
|
|
1219
|
+
client = connect_to(
|
|
1220
|
+
aws_access_key_id=account_id, region_name=region_name
|
|
1221
|
+
).cloudformation
|
|
1222
|
+
client.get_waiter("stack_create_complete").wait(StackName=stack_name)
|
|
1223
|
+
|
|
1224
|
+
# record operation
|
|
1225
|
+
operation = StackSetOperation(
|
|
1226
|
+
OperationId=op_id,
|
|
1227
|
+
StackSetId=stack_set.stack_set_id,
|
|
1228
|
+
Action=StackSetOperationAction.CREATE,
|
|
1229
|
+
Status=StackSetOperationStatus.SUCCEEDED,
|
|
1230
|
+
)
|
|
1231
|
+
stack_set.operations[op_id] = operation
|
|
1232
|
+
|
|
1233
|
+
return CreateStackInstancesOutput(OperationId=op_id)
|
|
1234
|
+
|
|
1235
|
+
@handler("DescribeStackSetOperation")
|
|
1236
|
+
def describe_stack_set_operation(
|
|
1237
|
+
self,
|
|
1238
|
+
context: RequestContext,
|
|
1239
|
+
stack_set_name: StackSetName,
|
|
1240
|
+
operation_id: ClientRequestToken,
|
|
1241
|
+
call_as: CallAs = None,
|
|
1242
|
+
**kwargs,
|
|
1243
|
+
) -> DescribeStackSetOperationOutput:
|
|
1244
|
+
state = get_cloudformation_store(context.account_id, context.region)
|
|
1245
|
+
stack_set = find_stack_set_v2(state, stack_set_name)
|
|
1246
|
+
if not stack_set:
|
|
1247
|
+
raise StackSetNotFoundError(stack_set_name)
|
|
1248
|
+
|
|
1249
|
+
result = stack_set.operations.get(operation_id)
|
|
1250
|
+
if not result:
|
|
1251
|
+
LOG.debug(
|
|
1252
|
+
'Unable to find operation ID "%s" for stack set "%s" in list: %s',
|
|
1253
|
+
operation_id,
|
|
1254
|
+
stack_set_name,
|
|
1255
|
+
list(stack_set.operations.keys()),
|
|
1256
|
+
)
|
|
1257
|
+
# TODO: proper exception
|
|
1258
|
+
raise ValueError(
|
|
1259
|
+
f'Unable to find operation ID "{operation_id}" for stack set "{stack_set_name}"'
|
|
1260
|
+
)
|
|
1261
|
+
|
|
1262
|
+
return DescribeStackSetOperationOutput(StackSetOperation=result)
|
|
1263
|
+
|
|
1264
|
+
@handler("DeleteStackInstances", expand=False)
|
|
1265
|
+
def delete_stack_instances(
|
|
1266
|
+
self,
|
|
1267
|
+
context: RequestContext,
|
|
1268
|
+
request: DeleteStackInstancesInput,
|
|
1269
|
+
) -> DeleteStackInstancesOutput:
|
|
1270
|
+
state = get_cloudformation_store(context.account_id, context.region)
|
|
1271
|
+
|
|
1272
|
+
stack_set_name = request["StackSetName"]
|
|
1273
|
+
stack_set = find_stack_set_v2(state, stack_set_name)
|
|
1274
|
+
if not stack_set:
|
|
1275
|
+
raise StackSetNotFoundError(stack_set_name)
|
|
1276
|
+
|
|
1277
|
+
op_id = request.get("OperationId") or short_uid()
|
|
1278
|
+
|
|
1279
|
+
accounts = request["Accounts"]
|
|
1280
|
+
regions = request["Regions"]
|
|
1281
|
+
|
|
1282
|
+
operations_to_await = []
|
|
1283
|
+
for account in accounts:
|
|
1284
|
+
for region in regions:
|
|
1285
|
+
cf_client = connect_to(aws_access_key_id=account, region_name=region).cloudformation
|
|
1286
|
+
instance = find_stack_instance(stack_set, account, region)
|
|
1287
|
+
|
|
1288
|
+
# TODO: check parity with AWS
|
|
1289
|
+
# TODO: delete stack instance?
|
|
1290
|
+
if not instance:
|
|
1291
|
+
continue
|
|
1292
|
+
|
|
1293
|
+
cf_client.delete_stack(StackName=instance.stack_id)
|
|
1294
|
+
operations_to_await.append(instance)
|
|
1295
|
+
|
|
1296
|
+
for instance in operations_to_await:
|
|
1297
|
+
cf_client = connect_to(
|
|
1298
|
+
aws_access_key_id=instance.account_id, region_name=instance.region_name
|
|
1299
|
+
).cloudformation
|
|
1300
|
+
cf_client.get_waiter("stack_delete_complete").wait(StackName=instance.stack_id)
|
|
1301
|
+
stack_set.stack_instances.remove(instance)
|
|
1302
|
+
|
|
1303
|
+
# record operation
|
|
1304
|
+
operation = StackSetOperation(
|
|
1305
|
+
OperationId=op_id,
|
|
1306
|
+
StackSetId=stack_set.stack_set_id,
|
|
1307
|
+
Action=StackSetOperationAction.DELETE,
|
|
1308
|
+
Status=StackSetOperationStatus.SUCCEEDED,
|
|
1309
|
+
)
|
|
1310
|
+
stack_set.operations[op_id] = operation
|
|
1311
|
+
|
|
1312
|
+
return DeleteStackInstancesOutput(OperationId=op_id)
|
|
1313
|
+
|
|
1314
|
+
@handler("DeleteStackSet")
|
|
1315
|
+
def delete_stack_set(
|
|
1316
|
+
self,
|
|
1317
|
+
context: RequestContext,
|
|
1318
|
+
stack_set_name: StackSetName,
|
|
1319
|
+
call_as: CallAs = None,
|
|
1320
|
+
**kwargs,
|
|
1321
|
+
) -> DeleteStackSetOutput:
|
|
1322
|
+
state = get_cloudformation_store(context.account_id, context.region)
|
|
1323
|
+
stack_set = find_stack_set_v2(state, stack_set_name)
|
|
1324
|
+
if not stack_set:
|
|
1325
|
+
# operation is idempotent
|
|
1326
|
+
return DeleteStackSetOutput()
|
|
1327
|
+
|
|
1328
|
+
# clean up any left-over instances
|
|
1329
|
+
operations_to_await = []
|
|
1330
|
+
for instance in stack_set.stack_instances:
|
|
1331
|
+
cf_client = connect_to(
|
|
1332
|
+
aws_access_key_id=instance.account_id, region_name=instance.region_name
|
|
1333
|
+
).cloudformation
|
|
1334
|
+
cf_client.delete_stack(StackName=instance.stack_id)
|
|
1335
|
+
operations_to_await.append(instance)
|
|
1336
|
+
|
|
1337
|
+
for instance in operations_to_await:
|
|
1338
|
+
cf_client = connect_to(
|
|
1339
|
+
aws_access_key_id=instance.account_id, region_name=instance.region_name
|
|
1340
|
+
).cloudformation
|
|
1341
|
+
cf_client.get_waiter("stack_delete_complete").wait(StackName=instance.stack_id)
|
|
1342
|
+
stack_set.stack_instances.remove(instance)
|
|
1343
|
+
|
|
1344
|
+
state.stack_sets_v2.pop(stack_set.stack_set_id)
|
|
1345
|
+
|
|
1346
|
+
return DeleteStackSetOutput()
|
|
1347
|
+
|
|
670
1348
|
@handler("DescribeStackEvents")
|
|
671
1349
|
def describe_stack_events(
|
|
672
1350
|
self,
|
|
@@ -696,10 +1374,19 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
696
1374
|
) -> GetTemplateOutput:
|
|
697
1375
|
state = get_cloudformation_store(context.account_id, context.region)
|
|
698
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
|
+
|
|
699
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")
|
|
700
1383
|
stack = change_set.stack
|
|
701
1384
|
elif stack_name:
|
|
702
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
|
+
)
|
|
703
1390
|
else:
|
|
704
1391
|
raise StackNotFoundError(stack_name)
|
|
705
1392
|
|
|
@@ -726,6 +1413,12 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
726
1413
|
stack = find_stack_v2(state, stack_name)
|
|
727
1414
|
if not stack:
|
|
728
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
|
+
|
|
729
1422
|
template = stack.template
|
|
730
1423
|
else:
|
|
731
1424
|
template_body = request.get("TemplateBody")
|
|
@@ -747,6 +1440,11 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
747
1440
|
template = template_preparer.parse_template(template_body)
|
|
748
1441
|
|
|
749
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
|
+
|
|
750
1448
|
for resource_id, resource in template["Resources"].items():
|
|
751
1449
|
res_type = resource["Type"]
|
|
752
1450
|
id_summaries[res_type].append(resource_id)
|
|
@@ -850,18 +1548,24 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
850
1548
|
raise RuntimeError("Multiple stacks matched, update matching logic")
|
|
851
1549
|
stack = active_stack_candidates[0]
|
|
852
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
|
+
|
|
853
1559
|
# TODO: proper status modeling
|
|
854
1560
|
before_parameters = stack.resolved_parameters
|
|
855
1561
|
# TODO: reconsider the way parameters are modelled in the update graph process.
|
|
856
1562
|
# The options might be reduce to using the current style, or passing the extra information
|
|
857
1563
|
# as a metadata object. The choice should be made considering when the extra information
|
|
858
1564
|
# is needed for the update graph building, or only looked up in downstream tasks (metadata).
|
|
859
|
-
request_parameters = request.get("Parameters",
|
|
1565
|
+
request_parameters = request.get("Parameters", [])
|
|
860
1566
|
# TODO: handle parameter defaults and resolution
|
|
861
|
-
after_parameters
|
|
862
|
-
|
|
863
|
-
for parameter in request_parameters
|
|
864
|
-
}
|
|
1567
|
+
after_parameters = self._extract_after_parameters(request_parameters, before_parameters)
|
|
1568
|
+
|
|
865
1569
|
before_template = stack.template
|
|
866
1570
|
after_template = structured_template
|
|
867
1571
|
|
|
@@ -873,6 +1577,7 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
873
1577
|
change_set = ChangeSet(
|
|
874
1578
|
stack,
|
|
875
1579
|
{"ChangeSetName": f"cs-{stack_name}-create", "ChangeSetType": ChangeSetType.CREATE},
|
|
1580
|
+
template_body=template_body,
|
|
876
1581
|
template=after_template,
|
|
877
1582
|
)
|
|
878
1583
|
self._setup_change_set_model(
|
|
@@ -887,7 +1592,7 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
887
1592
|
# TODO: some changes are only detectable at runtime; consider using
|
|
888
1593
|
# the ChangeSetModelDescriber, or a new custom visitors, to
|
|
889
1594
|
# pick-up on runtime changes.
|
|
890
|
-
if change_set.
|
|
1595
|
+
if not change_set.has_changes():
|
|
891
1596
|
raise ValidationError("No updates are to be performed.")
|
|
892
1597
|
|
|
893
1598
|
stack.set_stack_status(StackStatus.UPDATE_IN_PROGRESS)
|
|
@@ -898,20 +1603,51 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
898
1603
|
result = change_set_executor.execute()
|
|
899
1604
|
stack.set_stack_status(StackStatus.UPDATE_COMPLETE)
|
|
900
1605
|
stack.resolved_resources = result.resources
|
|
901
|
-
stack.resolved_parameters = result.parameters
|
|
902
1606
|
stack.resolved_outputs = result.outputs
|
|
903
1607
|
# if the deployment succeeded, update the stack's template representation to that
|
|
904
1608
|
# which was just deployed
|
|
905
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"]
|
|
906
1616
|
except Exception as e:
|
|
907
|
-
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
|
+
)
|
|
908
1622
|
stack.set_stack_status(StackStatus.UPDATE_FAILED)
|
|
909
1623
|
|
|
910
1624
|
start_worker_thread(_run)
|
|
911
1625
|
|
|
912
|
-
# TODO: stack id
|
|
913
1626
|
return UpdateStackOutput(StackId=stack.stack_id)
|
|
914
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
|
+
|
|
915
1651
|
@handler("DeleteStack")
|
|
916
1652
|
def delete_stack(
|
|
917
1653
|
self,
|
|
@@ -933,40 +1669,45 @@ class CloudformationProviderV2(CloudformationProvider):
|
|
|
933
1669
|
# created, but never executed
|
|
934
1670
|
if stack.status == StackStatus.REVIEW_IN_PROGRESS and not stack.resolved_resources:
|
|
935
1671
|
stack.set_stack_status(StackStatus.DELETE_COMPLETE)
|
|
936
|
-
stack.deletion_time = datetime.now(tz=
|
|
1672
|
+
stack.deletion_time = datetime.now(tz=UTC)
|
|
937
1673
|
return
|
|
938
1674
|
|
|
939
|
-
|
|
940
|
-
if stack.change_set_id:
|
|
941
|
-
if previous_change_set := find_change_set_v2(state, stack.change_set_id):
|
|
942
|
-
previous_update_model = previous_change_set.update_model
|
|
1675
|
+
stack.set_stack_status(StackStatus.DELETE_IN_PROGRESS)
|
|
943
1676
|
|
|
944
1677
|
# create a dummy change set
|
|
945
|
-
change_set = ChangeSet(
|
|
1678
|
+
change_set = ChangeSet(
|
|
1679
|
+
stack, {"ChangeSetName": f"delete-stack_{stack.stack_name}"}, template_body=""
|
|
1680
|
+
) # noqa
|
|
946
1681
|
self._setup_change_set_model(
|
|
947
1682
|
change_set=change_set,
|
|
948
|
-
before_template=stack.
|
|
1683
|
+
before_template=stack.processed_template,
|
|
949
1684
|
after_template=None,
|
|
950
1685
|
before_parameters=stack.resolved_parameters,
|
|
951
1686
|
after_parameters=None,
|
|
952
|
-
previous_update_model=previous_update_model,
|
|
953
1687
|
)
|
|
954
1688
|
|
|
955
1689
|
change_set_executor = ChangeSetModelExecutor(change_set)
|
|
956
1690
|
|
|
957
1691
|
def _run(*args):
|
|
958
1692
|
try:
|
|
959
|
-
stack.set_stack_status(StackStatus.DELETE_IN_PROGRESS)
|
|
960
1693
|
change_set_executor.execute()
|
|
961
1694
|
stack.set_stack_status(StackStatus.DELETE_COMPLETE)
|
|
962
|
-
stack.deletion_time = datetime.now(tz=
|
|
1695
|
+
stack.deletion_time = datetime.now(tz=UTC)
|
|
963
1696
|
except Exception as e:
|
|
964
1697
|
LOG.warning(
|
|
965
1698
|
"Failed to delete stack '%s': %s",
|
|
966
1699
|
stack.stack_name,
|
|
967
1700
|
e,
|
|
968
|
-
exc_info=LOG.isEnabledFor(logging.DEBUG),
|
|
1701
|
+
exc_info=LOG.isEnabledFor(logging.DEBUG) and config.CFN_VERBOSE_ERRORS,
|
|
969
1702
|
)
|
|
970
1703
|
stack.set_stack_status(StackStatus.DELETE_FAILED)
|
|
971
1704
|
|
|
972
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())
|