localstack-core 4.6.1.dev74__py3-none-any.whl → 4.10.1.dev7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of localstack-core might be problematic. Click here for more details.

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