localstack-core 4.6.1.dev60__py3-none-any.whl → 4.10.1.dev12__py3-none-any.whl

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