@kensio/yulin 1.20.15 → 1.20.17

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 (355) hide show
  1. package/README.md +13 -0
  2. package/dist/sdk/module/sim-sdk-module-client-interceptor.d.ts +5 -0
  3. package/dist/sdk/module/sim-sdk-module-client-interceptor.js +14 -0
  4. package/dist/sdk/module/sim-sdk-static-client-factory.d.ts +26 -0
  5. package/dist/sdk/module/sim-sdk-static-client-factory.js +59 -0
  6. package/dist/sdk/send-patch.d.ts +8 -0
  7. package/dist/sdk/send-patch.js +11 -0
  8. package/dist/service/aws/caller/sim-aws-ambient-caller.d.ts +23 -0
  9. package/dist/service/aws/caller/sim-aws-ambient-caller.js +12 -0
  10. package/dist/service/aws/caller/sim-aws-caller-resolver.d.ts +39 -6
  11. package/dist/service/aws/caller/sim-aws-caller-resolver.js +32 -30
  12. package/dist/service/aws/caller/sim-aws-caller.d.ts +9 -0
  13. package/dist/service/aws/caller/sim-aws-resolved-caller.d.ts +19 -0
  14. package/dist/service/aws/caller/sim-aws-resolved-caller.js +26 -0
  15. package/dist/service/aws/factory/sim-aws-account-region-service-builder.d.ts +12 -0
  16. package/dist/service/aws/factory/sim-aws-account-region-service-builder.js +6 -0
  17. package/dist/service/aws/factory/sim-aws-account-service-cache.d.ts +12 -0
  18. package/dist/service/aws/factory/sim-aws-account-service-cache.js +9 -0
  19. package/dist/service/aws/factory/sim-aws-registered-service-builder.js +3 -1
  20. package/dist/service/aws/factory/sim-aws-service-factory.d.ts +5 -0
  21. package/dist/service/aws/factory/sim-aws-service-factory.js +14 -0
  22. package/dist/service/aws/sim-aws-properties.d.ts +15 -0
  23. package/dist/service/aws/sim-aws.js +2 -1
  24. package/dist/service/cloudformation/command/create-stack/create-stack.handler.d.ts +4 -0
  25. package/dist/service/cloudformation/command/create-stack/create-stack.handler.js +4 -1
  26. package/dist/service/cloudformation/deploy/sim-cfn-cdk-out-deployer.js +2 -1
  27. package/dist/service/cloudformation/deploy/sim-cfn-cdk-out-plan.d.ts +11 -2
  28. package/dist/service/cloudformation/deploy/sim-cfn-cdk-out-plan.js +2 -1
  29. package/dist/service/cloudformation/deploy/sim-cfn-cdk-out-stack-options.d.ts +6 -0
  30. package/dist/service/cloudformation/deploy/sim-cfn-template-deployer.d.ts +11 -0
  31. package/dist/service/cloudformation/deploy/sim-cfn-template-deployer.js +17 -15
  32. package/dist/service/cloudformation/deploy/sim-cfn-template-file-loader.d.ts +12 -0
  33. package/dist/service/cloudformation/deploy/sim-cfn-template-file-loader.js +2 -1
  34. package/dist/service/cloudformation/resource/cfn/cloudfront/sim-cloudfront-cache-policy-cfn.d.ts +23 -0
  35. package/dist/service/cloudformation/resource/cfn/cloudfront/sim-cloudfront-cache-policy-cfn.js +30 -0
  36. package/dist/service/cloudformation/resource/cfn/cloudfront/sim-cloudfront-cfn-value-adapter.js +8 -0
  37. package/dist/service/cloudformation/resource/name/sim-cfn-generated-resource-name.d.ts +29 -12
  38. package/dist/service/cloudformation/resource/name/sim-cfn-generated-resource-name.js +51 -20
  39. package/dist/service/cloudformation/stack/deploy/sim-cfn-resource-order.d.ts +19 -0
  40. package/dist/service/cloudformation/stack/deploy/sim-cfn-resource-order.js +6 -0
  41. package/dist/service/cloudformation/stack/deploy/sim-cfn-stack-resource-batch-creator.d.ts +7 -0
  42. package/dist/service/cloudformation/stack/deploy/sim-cfn-stack-resource-batch-creator.js +9 -2
  43. package/dist/service/cloudformation/stack/deploy/sim-cfn-stack-resource-creator.d.ts +2 -0
  44. package/dist/service/cloudformation/stack/deploy/sim-cfn-stack-resource-creator.js +2 -1
  45. package/dist/service/cloudformation/stack/sim-cfn-stack-resource-operations.d.ts +6 -0
  46. package/dist/service/cloudformation/stack/sim-cfn-stack-resource-operations.js +4 -1
  47. package/dist/service/cloudformation/stack/sim-cfn-stack.js +2 -1
  48. package/dist/service/cloudformation/stack/sim-cfn-stack.type.d.ts +6 -0
  49. package/dist/service/cloudfront/behaviour/sim-cloud-front-behavior.d.ts +5 -0
  50. package/dist/service/cloudfront/cache-policy/sim-cf-cache-policy-registry.d.ts +37 -0
  51. package/dist/service/cloudfront/cache-policy/sim-cf-cache-policy-registry.js +50 -0
  52. package/dist/service/cloudfront/cache-policy/sim-cf-cache-policy.d.ts +25 -0
  53. package/dist/service/cloudfront/cache-policy/sim-cf-cache-policy.js +21 -0
  54. package/dist/service/cloudfront/cache-policy/sim-cf-managed-cache-policies.d.ts +28 -0
  55. package/dist/service/cloudfront/cache-policy/sim-cf-managed-cache-policies.js +48 -0
  56. package/dist/service/cloudfront/cff/function-code-input/cff-function-code-input.d.ts +8 -0
  57. package/dist/service/cloudfront/cff/function-code-input/cff-function-code-input.js +13 -0
  58. package/dist/service/cloudfront/cff/sim-cf-function-commands.d.ts +52 -0
  59. package/dist/service/cloudfront/cff/sim-cf-function-commands.js +51 -0
  60. package/dist/service/cloudfront/cff/sim-cff-configuration.d.ts +41 -0
  61. package/dist/service/cloudfront/cff/sim-cff-configuration.js +34 -0
  62. package/dist/service/cloudfront/cff/sim-cff-stage.d.ts +23 -0
  63. package/dist/service/cloudfront/cff/sim-cff-stage.js +30 -0
  64. package/dist/service/cloudfront/cff/sim-cloudfront-function.d.ts +6 -1
  65. package/dist/service/cloudfront/cff/sim-cloudfront-function.js +6 -0
  66. package/dist/service/cloudfront/cfn/cache-policy/sim-cfn-cf-cache-policy-config.d.ts +27 -0
  67. package/dist/service/cloudfront/cfn/cache-policy/sim-cfn-cf-cache-policy-config.js +42 -0
  68. package/dist/service/cloudfront/cfn/cache-policy/sim-cfn-cf-cache-policy-creator.d.ts +24 -0
  69. package/dist/service/cloudfront/cfn/cache-policy/sim-cfn-cf-cache-policy-creator.js +32 -0
  70. package/dist/service/cloudfront/cfn/distro/{sim-cfn-cf-distro-response-headers-policy.d.ts → sim-cfn-cf-distro-behavior-policies.d.ts} +8 -7
  71. package/dist/service/cloudfront/cfn/distro/{sim-cfn-cf-distro-response-headers-policy.js → sim-cfn-cf-distro-behavior-policies.js} +11 -37
  72. package/dist/service/cloudfront/cfn/distro/sim-cfn-cf-distro-creator.d.ts +6 -5
  73. package/dist/service/cloudfront/cfn/distro/sim-cfn-cf-distro-creator.js +8 -7
  74. package/dist/service/cloudfront/cfn/distro/sim-cfn-cf-distro-policy-drops.d.ts +23 -0
  75. package/dist/service/cloudfront/cfn/distro/sim-cfn-cf-distro-policy-drops.js +50 -0
  76. package/dist/service/cloudfront/cfn/sim-cfn-cloudfront-resource-factory.d.ts +1 -0
  77. package/dist/service/cloudfront/cfn/sim-cfn-cloudfront-resource-factory.js +10 -0
  78. package/dist/service/cloudfront/command/create-distribution/create-distribution.command.d.ts +1 -0
  79. package/dist/service/cloudfront/command/create-distribution/create-distribution.handler.d.ts +2 -0
  80. package/dist/service/cloudfront/command/create-function/create-function.handler.js +7 -1
  81. package/dist/service/cloudfront/command/function/sim-cf-describe-function.d.ts +22 -0
  82. package/dist/service/cloudfront/command/function/sim-cf-describe-function.js +32 -0
  83. package/dist/service/cloudfront/command/function/sim-cf-function-access.d.ts +48 -0
  84. package/dist/service/cloudfront/command/function/sim-cf-function-access.js +48 -0
  85. package/dist/service/cloudfront/command/function/sim-cf-function-command.types.d.ts +97 -0
  86. package/dist/service/cloudfront/command/function/sim-cf-function-command.types.js +10 -0
  87. package/dist/service/cloudfront/command/function/sim-cf-function-lookup.d.ts +12 -0
  88. package/dist/service/cloudfront/command/function/sim-cf-function-lookup.js +21 -0
  89. package/dist/service/cloudfront/command/function/sim-cf-function-summary.d.ts +11 -0
  90. package/dist/service/cloudfront/command/function/sim-cf-function-summary.js +30 -0
  91. package/dist/service/cloudfront/command/function/sim-cf-get-function.d.ts +23 -0
  92. package/dist/service/cloudfront/command/function/sim-cf-get-function.js +33 -0
  93. package/dist/service/cloudfront/command/function/sim-cf-list-functions.d.ts +22 -0
  94. package/dist/service/cloudfront/command/function/sim-cf-list-functions.js +38 -0
  95. package/dist/service/cloudfront/command/update-distribution/update-distribution.handler.d.ts +2 -0
  96. package/dist/service/cloudfront/distribution/configurator/sim-cf-behavior-cache-policy.d.ts +18 -0
  97. package/dist/service/cloudfront/distribution/configurator/sim-cf-behavior-cache-policy.js +30 -0
  98. package/dist/service/cloudfront/distribution/configurator/sim-cf-behavior-policies.d.ts +28 -0
  99. package/dist/service/cloudfront/distribution/configurator/sim-cf-behavior-policies.js +41 -0
  100. package/dist/service/cloudfront/distribution/configurator/sim-cf-behavior-properties.d.ts +2 -2
  101. package/dist/service/cloudfront/distribution/configurator/sim-cf-behavior-properties.js +6 -3
  102. package/dist/service/cloudfront/distribution/configurator/sim-cf-behavior-response-headers-policy.d.ts +0 -10
  103. package/dist/service/cloudfront/distribution/configurator/sim-cf-behavior-response-headers-policy.js +0 -19
  104. package/dist/service/cloudfront/distribution/configurator/sim-cf-distribution-configurator.factory.d.ts +2 -0
  105. package/dist/service/cloudfront/distribution/configurator/sim-cf-distribution-configurator.factory.js +4 -2
  106. package/dist/service/cloudfront/distribution/configurator/sim-cloud-front-behavior-configurator.d.ts +3 -3
  107. package/dist/service/cloudfront/distribution/configurator/sim-cloud-front-behavior-configurator.js +5 -5
  108. package/dist/service/cloudfront/distribution/configurator/sim-cloud-front-distribution-configurator.d.ts +3 -3
  109. package/dist/service/cloudfront/distribution/configurator/sim-cloud-front-distribution-configurator.js +4 -4
  110. package/dist/service/cloudfront/distribution/sim-cf-distribution-configuration-state.d.ts +3 -1
  111. package/dist/service/cloudfront/distribution/sim-cf-distribution-reconfigurer.d.ts +2 -0
  112. package/dist/service/cloudfront/error/sim-cloudfront.error.d.ts +21 -0
  113. package/dist/service/cloudfront/error/sim-cloudfront.error.js +25 -0
  114. package/dist/service/cloudfront/key-value-store/sim-cf-key-value-store-access.js +2 -10
  115. package/dist/service/cloudfront/key-value-store/sim-cf-key-value-store.js +2 -1
  116. package/dist/service/cloudfront/sdk/sim-cloudfront-sdk-command-router.js +12 -0
  117. package/dist/service/cloudfront/sim-cf-authorize.d.ts +16 -0
  118. package/dist/service/cloudfront/sim-cf-authorize.js +19 -0
  119. package/dist/service/cloudfront/sim-cf-etag.d.ts +7 -0
  120. package/dist/service/cloudfront/sim-cf-etag.js +10 -0
  121. package/dist/service/cloudfront/sim-cloudfront-commands.d.ts +8 -12
  122. package/dist/service/cloudfront/sim-cloudfront-commands.js +8 -17
  123. package/dist/service/cloudfront/sim-cloudfront-policies.d.ts +43 -0
  124. package/dist/service/cloudfront/sim-cloudfront-policies.js +53 -0
  125. package/dist/service/cloudfront/sim-cloudfront.d.ts +13 -17
  126. package/dist/service/cloudfront/sim-cloudfront.js +22 -25
  127. package/dist/service/ecr/authorize/sim-ecr-authorizer.d.ts +31 -0
  128. package/dist/service/ecr/authorize/sim-ecr-authorizer.js +38 -0
  129. package/dist/service/ecr/cfn/repository/sim-cfn-ecr-repository-creator.d.ts +11 -2
  130. package/dist/service/ecr/cfn/repository/sim-cfn-ecr-repository-creator.js +11 -2
  131. package/dist/service/ecr/cfn/sim-ecr-cfn-resource-factory.d.ts +4 -2
  132. package/dist/service/ecr/cfn/sim-ecr-cfn-resource-factory.js +8 -9
  133. package/dist/service/ecr/cfn/sim-ecr-cfn-unsupported-resource.d.ts +7 -0
  134. package/dist/service/ecr/cfn/sim-ecr-cfn-unsupported-resource.js +9 -0
  135. package/dist/service/ecr/sim-ecr.d.ts +7 -0
  136. package/dist/service/ecr/sim-ecr.js +16 -4
  137. package/dist/service/ecs/command/authorize/sim-ecs-authorizer.d.ts +12 -0
  138. package/dist/service/ecs/command/authorize/sim-ecs-authorizer.js +21 -0
  139. package/dist/service/ecs/command/register-task-definition/register-task-definition.handler.js +1 -0
  140. package/dist/service/ecs/sim-ecs-service-principal.d.ts +8 -0
  141. package/dist/service/ecs/sim-ecs-service-principal.js +8 -0
  142. package/dist/service/eventbridge/command/authorize/sim-event-bridge-authorizer.d.ts +12 -0
  143. package/dist/service/eventbridge/command/authorize/sim-event-bridge-authorizer.js +21 -0
  144. package/dist/service/eventbridge/command/rule/sim-event-bridge-rule-access.d.ts +13 -0
  145. package/dist/service/eventbridge/command/rule/sim-event-bridge-rule-access.js +9 -0
  146. package/dist/service/eventbridge/command/target/sim-event-bridge-put-targets.js +1 -0
  147. package/dist/service/firehose/command/authorize/sim-firehose-authorizer.d.ts +12 -0
  148. package/dist/service/firehose/command/authorize/sim-firehose-authorizer.js +20 -0
  149. package/dist/service/firehose/command/sim-firehose-delivery-stream-access.d.ts +4 -0
  150. package/dist/service/firehose/command/sim-firehose-delivery-stream-access.js +6 -0
  151. package/dist/service/firehose/command/stream/sim-firehose-create-delivery-stream.js +3 -1
  152. package/dist/service/firehose/sim-firehose-service-principal.d.ts +8 -0
  153. package/dist/service/firehose/sim-firehose-service-principal.js +8 -0
  154. package/dist/service/firehose/source/sim-firehose-source.d.ts +5 -0
  155. package/dist/service/firehose/source/sim-firehose-source.js +5 -0
  156. package/dist/service/glue/cfn/database/sim-cfn-glue-database-properties.js +2 -2
  157. package/dist/service/glue/cfn/sim-cfn-glue-generated-name.d.ts +10 -0
  158. package/dist/service/glue/cfn/sim-cfn-glue-generated-name.js +25 -0
  159. package/dist/service/glue/cfn/table/sim-cfn-glue-table-properties.js +2 -3
  160. package/dist/service/iam/authorize/allow/sim-iam-allow-requirement.d.ts +33 -2
  161. package/dist/service/iam/authorize/allow/sim-iam-allow-requirement.js +33 -2
  162. package/dist/service/iam/authorize/allow/sim-iam-allow-statements.d.ts +4 -3
  163. package/dist/service/iam/authorize/allow/sim-iam-allow-statements.js +9 -3
  164. package/dist/service/iam/authorize/context/sim-iam-auth-z-allow-requirement.d.ts +14 -5
  165. package/dist/service/iam/authorize/context/sim-iam-auth-z-allow-requirement.js +16 -12
  166. package/dist/service/iam/authorize/context/sim-iam-auth-z-caller-context-builder.d.ts +36 -7
  167. package/dist/service/iam/authorize/context/sim-iam-auth-z-caller-context-builder.js +22 -11
  168. package/dist/service/iam/authorize/context/sim-iam-auth-z-context-builder.d.ts +10 -71
  169. package/dist/service/iam/authorize/context/sim-iam-auth-z-context-builder.js +7 -2
  170. package/dist/service/iam/authorize/context/sim-iam-auth-z-input.d.ts +87 -0
  171. package/dist/service/iam/authorize/context/sim-iam-auth-z-input.js +1 -0
  172. package/dist/service/iam/authorize/match/sim-iam-policy-principal-matcher.d.ts +21 -3
  173. package/dist/service/iam/authorize/match/sim-iam-policy-principal-matcher.js +34 -10
  174. package/dist/service/iam/authorize/match/sim-iam-principal-match.d.ts +29 -8
  175. package/dist/service/iam/authorize/match/sim-iam-principal-match.js +38 -13
  176. package/dist/service/iam/authorize/pass-role/sim-iam-pass-role-authorizer.d.ts +74 -0
  177. package/dist/service/iam/authorize/pass-role/sim-iam-pass-role-authorizer.js +92 -0
  178. package/dist/service/iam/authorize/sim-iam-account-auth-z.d.ts +21 -5
  179. package/dist/service/iam/authorize/sim-iam-account-auth-z.js +7 -3
  180. package/dist/service/iam/authorize/sim-iam-authorizer.d.ts +5 -2
  181. package/dist/service/iam/authorize/sim-iam-authorizer.js +3 -1
  182. package/dist/service/iam/authorize/sim-iam-inter-service-auth-z.d.ts +1 -1
  183. package/dist/service/iam/authorize/sim-iam-wildcard.js +39 -4
  184. package/dist/service/iam/cfn/managed-policy/sim-cfn-iam-managed-policy-creator.js +2 -1
  185. package/dist/service/iam/cfn/name/sim-cfn-iam-generated-name.d.ts +16 -0
  186. package/dist/service/iam/cfn/name/sim-cfn-iam-generated-name.js +36 -0
  187. package/dist/service/iam/cfn/role/sim-cfn-iam-role-properties-parser.js +9 -8
  188. package/dist/service/iam/cfn/sim-cfn-iam-optional-string.d.ts +17 -0
  189. package/dist/service/iam/cfn/sim-cfn-iam-optional-string.js +17 -0
  190. package/dist/service/iam/cfn/user/sim-cfn-iam-user-properties-parser.js +4 -8
  191. package/dist/service/iam/command/policy/create-policy/create-policy-input-resolver.js +2 -0
  192. package/dist/service/iam/command/policy/put-role-policy/put-role-policy.handler.js +5 -0
  193. package/dist/service/iam/command/policy/put-user-policy/put-user-policy.handler.js +5 -0
  194. package/dist/service/iam/command/role/create-role/create-role.handler.js +4 -1
  195. package/dist/service/iam/error/sim-iam.error.d.ts +11 -0
  196. package/dist/service/iam/error/sim-iam.error.js +13 -0
  197. package/dist/service/iam/index.d.ts +1 -0
  198. package/dist/service/iam/policy/sim-iam-policy-document-of-size.d.ts +18 -0
  199. package/dist/service/iam/policy/sim-iam-policy-document-of-size.js +41 -0
  200. package/dist/service/iam/role/sim-iam-deploy-role.d.ts +56 -0
  201. package/dist/service/iam/role/sim-iam-deploy-role.js +50 -0
  202. package/dist/service/iam/role/sim-iam-role-with-policy.factory.d.ts +11 -0
  203. package/dist/service/iam/role/sim-iam-role-with-policy.factory.js +4 -2
  204. package/dist/service/iam/sim-iam-account-parts.d.ts +16 -0
  205. package/dist/service/iam/sim-iam-account-parts.js +2 -0
  206. package/dist/service/iam/sim-iam.d.ts +12 -2
  207. package/dist/service/iam/sim-iam.js +12 -0
  208. package/dist/service/iam/validate/size/sim-iam-policy-document-size.d.ts +57 -0
  209. package/dist/service/iam/validate/size/sim-iam-policy-document-size.js +71 -0
  210. package/dist/service/kms/command/authorize/sim-kms-authorizer.d.ts +3 -1
  211. package/dist/service/kms/command/authorize/sim-kms-authorizer.js +4 -1
  212. package/dist/service/kms/command/sim-kms-request-options.d.ts +11 -0
  213. package/dist/service/kms/key/sim-kms-key-policy.d.ts +5 -4
  214. package/dist/service/kms/key/sim-kms-key-policy.js +4 -3
  215. package/dist/service/lambda/cfn/function/sim-cfn-lambda-function-properties-parser.d.ts +2 -2
  216. package/dist/service/lambda/cfn/function/sim-cfn-lambda-function-properties-parser.js +4 -3
  217. package/dist/service/lambda/cfn/function/sim-cfn-lambda-generated-function-name.d.ts +10 -0
  218. package/dist/service/lambda/cfn/function/sim-cfn-lambda-generated-function-name.js +25 -0
  219. package/dist/service/lambda/command/authorize/sim-lambda-resource-policies.d.ts +1 -1
  220. package/dist/service/lambda/command/create-function/create-function-authorizer.d.ts +9 -2
  221. package/dist/service/lambda/command/create-function/create-function-authorizer.js +16 -2
  222. package/dist/service/lambda/command/create-function/create-function.handler.js +1 -1
  223. package/dist/service/lambda/command/update-function-configuration/update-function-configuration-authorizer.d.ts +7 -2
  224. package/dist/service/lambda/command/update-function-configuration/update-function-configuration-authorizer.js +14 -2
  225. package/dist/service/lambda/command/update-function-configuration/update-function-configuration.handler.js +1 -1
  226. package/dist/service/lambda/function/logging/sim-lambda-function-logging.d.ts +10 -1
  227. package/dist/service/lambda/function/logging/sim-lambda-function-logging.js +25 -1
  228. package/dist/service/lambda/function/logging/sim-lambda-invoke-error-log.d.ts +13 -0
  229. package/dist/service/lambda/function/logging/sim-lambda-invoke-error-log.js +18 -0
  230. package/dist/service/lambda/sim-lambda-service-principal.d.ts +7 -0
  231. package/dist/service/lambda/sim-lambda-service-principal.js +7 -0
  232. package/dist/service/logs/cfn/delivery/sim-cfn-delivery-authorization.d.ts +46 -0
  233. package/dist/service/logs/cfn/delivery/sim-cfn-delivery-authorization.js +49 -0
  234. package/dist/service/logs/cfn/delivery/sim-cfn-delivery-creator.d.ts +5 -0
  235. package/dist/service/logs/cfn/delivery/sim-cfn-delivery-creator.js +6 -0
  236. package/dist/service/logs/cfn/delivery/sim-cfn-delivery-destination-creator.d.ts +5 -0
  237. package/dist/service/logs/cfn/delivery/sim-cfn-delivery-destination-creator.js +6 -0
  238. package/dist/service/logs/cfn/delivery/sim-cfn-delivery-source-creator.d.ts +5 -0
  239. package/dist/service/logs/cfn/delivery/sim-cfn-delivery-source-creator.js +6 -0
  240. package/dist/service/logs/cfn/group/sim-cfn-log-group-creator.d.ts +9 -2
  241. package/dist/service/logs/cfn/group/sim-cfn-log-group-creator.js +34 -5
  242. package/dist/service/logs/cfn/sim-logs-cfn-resource-deleter.js +1 -1
  243. package/dist/service/logs/cfn/sim-logs-cfn-resource-factory.d.ts +4 -0
  244. package/dist/service/logs/cfn/sim-logs-cfn-resource-factory.js +9 -4
  245. package/dist/service/logs/sim-logs-commands.d.ts +3 -0
  246. package/dist/service/logs/sim-logs-commands.js +4 -0
  247. package/dist/service/logs/sim-logs.js +6 -1
  248. package/dist/service/s3/cfn/bucket/sim-cfn-s3-bucket-creator.d.ts +2 -2
  249. package/dist/service/s3/cfn/bucket/sim-cfn-s3-bucket-creator.js +4 -3
  250. package/dist/service/s3/cfn/bucket/sim-cfn-s3-bucket-generated-name.d.ts +12 -0
  251. package/dist/service/s3/cfn/bucket/sim-cfn-s3-bucket-generated-name.js +22 -0
  252. package/dist/service/s3/command/authorize/sim-s3-bucket-resource-policies.d.ts +1 -1
  253. package/dist/service/scheduler/command/authorize/sim-scheduler-authorizer.d.ts +10 -1
  254. package/dist/service/scheduler/command/authorize/sim-scheduler-authorizer.js +19 -1
  255. package/dist/service/scheduler/command/schedule/sim-scheduler-create-schedule.js +1 -0
  256. package/dist/service/scheduler/command/schedule/sim-scheduler-schedule-access.d.ts +8 -0
  257. package/dist/service/scheduler/command/schedule/sim-scheduler-schedule-access.js +9 -0
  258. package/dist/service/scheduler/command/schedule/sim-scheduler-update-schedule.js +1 -0
  259. package/dist/service/secretsmanager/cfn/secret/sim-cfn-secrets-manager-generated-name.d.ts +15 -0
  260. package/dist/service/secretsmanager/cfn/secret/sim-cfn-secrets-manager-generated-name.js +22 -0
  261. package/dist/service/secretsmanager/cfn/secret/sim-cfn-secrets-manager-secret-properties.d.ts +4 -10
  262. package/dist/service/secretsmanager/cfn/secret/sim-cfn-secrets-manager-secret-properties.js +13 -31
  263. package/dist/service/secretsmanager/cfn/secret/sim-cfn-secrets-manager-secret-value.d.ts +17 -0
  264. package/dist/service/secretsmanager/cfn/secret/sim-cfn-secrets-manager-secret-value.js +30 -0
  265. package/dist/service/secretsmanager/secret/sim-secrets-manager-secret-name.d.ts +6 -0
  266. package/dist/service/secretsmanager/secret/sim-secrets-manager-secret-name.js +8 -3
  267. package/dist/service/sns/command/authorize/sim-sns-topic-resource-policies.d.ts +1 -1
  268. package/dist/service/sqs/command/authorize/sim-sqs-queue-resource-policies.d.ts +1 -1
  269. package/dist/service/ssm/cfn/parameter/sim-cfn-ssm-generated-parameter-name.d.ts +16 -0
  270. package/dist/service/ssm/cfn/parameter/sim-cfn-ssm-generated-parameter-name.js +23 -0
  271. package/dist/service/ssm/cfn/parameter/sim-cfn-ssm-parameter-properties.d.ts +2 -4
  272. package/dist/service/ssm/cfn/parameter/sim-cfn-ssm-parameter-properties.js +5 -5
  273. package/dist/service/ssm/parameter/sim-ssm-kms-crypto.d.ts +6 -4
  274. package/dist/service/ssm/parameter/sim-ssm-kms-crypto.js +4 -4
  275. package/dist/service/ssm/parameter/sim-ssm-parameter-arn.d.ts +17 -0
  276. package/dist/service/ssm/parameter/sim-ssm-parameter-arn.js +20 -0
  277. package/dist/service/ssm/parameter/sim-ssm-parameter-kms.d.ts +15 -9
  278. package/dist/service/ssm/parameter/sim-ssm-parameter-kms.js +20 -10
  279. package/dist/service/ssm/parameter/sim-ssm-parameter-name.js +3 -12
  280. package/dist/service/stepfunctions/command/authorize/sim-step-functions-authorizer.d.ts +41 -0
  281. package/dist/service/stepfunctions/command/authorize/sim-step-functions-authorizer.js +64 -0
  282. package/dist/service/stepfunctions/command/machine/sim-state-machine-create.d.ts +5 -2
  283. package/dist/service/stepfunctions/command/machine/sim-state-machine-create.js +6 -2
  284. package/dist/service/stepfunctions/command/machine/sim-state-machine-delete.d.ts +26 -0
  285. package/dist/service/stepfunctions/command/machine/sim-state-machine-delete.js +29 -0
  286. package/dist/service/stepfunctions/command/machine/sim-state-machine-lookup.d.ts +7 -0
  287. package/dist/service/stepfunctions/command/machine/sim-state-machine-lookup.js +12 -3
  288. package/dist/service/stepfunctions/command/machine/sim-state-machine-update.d.ts +19 -0
  289. package/dist/service/stepfunctions/command/machine/{sim-state-machine-writes.js → sim-state-machine-update.js} +3 -14
  290. package/dist/service/stepfunctions/sim-step-functions.d.ts +8 -2
  291. package/dist/service/stepfunctions/sim-step-functions.js +26 -9
  292. package/dist/service/sts/auth-z/assume-role-auth-z-coordinator.d.ts +9 -2
  293. package/dist/service/sts/auth-z/assume-role-auth-z-coordinator.js +2 -2
  294. package/dist/service/sts/auth-z/assume-role-source-account-auth-z.d.ts +10 -4
  295. package/dist/service/sts/auth-z/assume-role-source-account-auth-z.js +8 -7
  296. package/dist/service/sts/auth-z/assume-role-target-auth-z.d.ts +2 -2
  297. package/dist/service/sts/auth-z/assume-role-trust-policy-authorizer.d.ts +6 -2
  298. package/dist/service/sts/auth-z/assume-role-trust-policy-authorizer.js +14 -11
  299. package/dist/service/sts/command/assume-role/assume-role.handler.d.ts +10 -1
  300. package/dist/service/sts/command/assume-role/assume-role.handler.js +6 -2
  301. package/dist/service/sts/command/get-caller-identity/get-caller-identity.handler.d.ts +10 -1
  302. package/dist/service/sts/command/get-caller-identity/get-caller-identity.handler.js +5 -1
  303. package/dist/service/sts/service-role/sim-service-role.js +5 -1
  304. package/dist/service/sts/sim-sts.d.ts +18 -1
  305. package/dist/service/sts/sim-sts.js +8 -0
  306. package/dist/util/memo/bounded-memo.d.ts +36 -0
  307. package/dist/util/memo/bounded-memo.js +58 -0
  308. package/docs/README.md +54 -0
  309. package/docs/ai-skill/README.md +69 -0
  310. package/docs/cli/README.md +284 -0
  311. package/docs/factories/README.md +140 -0
  312. package/docs/lint/README.md +176 -0
  313. package/docs/non-aws-dependencies/README.md +261 -0
  314. package/docs/sdk/README.md +274 -0
  315. package/docs/serve/README.md +1126 -0
  316. package/docs/services/acm/README.md +823 -0
  317. package/docs/services/apigateway/README.md +1919 -0
  318. package/docs/services/apigatewayv2/README.md +2678 -0
  319. package/docs/services/athena/README.md +1044 -0
  320. package/docs/services/bedrock/README.md +350 -0
  321. package/docs/services/cloudformation/README.md +3625 -0
  322. package/docs/services/cloudfront/README.md +3098 -0
  323. package/docs/services/cloudwatch/README.md +422 -0
  324. package/docs/services/cognito/README.md +4924 -0
  325. package/docs/services/dynamodb/README.md +3785 -0
  326. package/docs/services/ecr/README.md +302 -0
  327. package/docs/services/ecs/README.md +2021 -0
  328. package/docs/services/elbv2/README.md +1901 -0
  329. package/docs/services/eventbridge/README.md +1118 -0
  330. package/docs/services/firehose/README.md +811 -0
  331. package/docs/services/glue/README.md +507 -0
  332. package/docs/services/iam/README.md +1578 -0
  333. package/docs/services/kinesis/README.md +455 -0
  334. package/docs/services/kms/README.md +664 -0
  335. package/docs/services/lambda/README.md +3719 -0
  336. package/docs/services/logs/README.md +882 -0
  337. package/docs/services/organizations/README.md +574 -0
  338. package/docs/services/personalize/README.md +835 -0
  339. package/docs/services/rekognition/README.md +1070 -0
  340. package/docs/services/route53/README.md +1783 -0
  341. package/docs/services/s3/README.md +3029 -0
  342. package/docs/services/scheduler/README.md +814 -0
  343. package/docs/services/secretsmanager/README.md +670 -0
  344. package/docs/services/ses/README.md +1013 -0
  345. package/docs/services/sns/README.md +1904 -0
  346. package/docs/services/sqs/README.md +1185 -0
  347. package/docs/services/ssm/README.md +1096 -0
  348. package/docs/services/stepfunctions/README.md +1341 -0
  349. package/docs/services/sts/README.md +245 -0
  350. package/docs/services/wafv2/README.md +1380 -0
  351. package/docs/terraform/README.md +248 -0
  352. package/docs/time/README.md +284 -0
  353. package/llms.txt +59 -0
  354. package/package.json +7 -4
  355. package/dist/service/stepfunctions/command/machine/sim-state-machine-writes.d.ts +0 -25
@@ -0,0 +1,2021 @@
1
+ # Simulated ECS
2
+
3
+ Yulin includes a simulated Amazon ECS for tests and local development. It holds clusters, task
4
+ definitions and services in memory, runs tasks from handlers you bind to their containers, and
5
+ authorizes every operation with simulated IAM.
6
+
7
+ ECS-specific types are imported from the `@kensio/yulin/ecs` subpath.
8
+
9
+ ## What Yulin does with a container image
10
+
11
+ Yulin never looks inside a container image, and it could not if it tried. An image may hold a Go
12
+ binary, nginx, Redis or anything else, and the only thing Yulin can run is JavaScript or TypeScript
13
+ in its own process.
14
+
15
+ So an image URI is only ever an identifier. Nothing here reads it, pulls it, or runs anything from
16
+ it. It is stored as declared, and it is what a container is matched on when a task runs, in the same
17
+ way an image URI identifies a container image Lambda function. The rule that follows is that a
18
+ container matched to a handler runs that handler, and a container with no match is recorded as not
19
+ simulated while the rest of the task carries on.
20
+
21
+ A realistic task definition holds an application container, a log router and an observability agent.
22
+ Only the first of those is something Yulin could ever run, and all three are stored and reported
23
+ back exactly as declared.
24
+
25
+ The case this leaves out is a sidecar the application depends on, such as a Redis or a database in
26
+ the same task. Point its connection details, which are ordinary environment variables, at a real one
27
+ you run yourself. See [non-AWS dependencies](https://yulinsim.dev/non-aws-dependencies/) for how that fits
28
+ together.
29
+
30
+ ## Registering a task definition
31
+
32
+ `RegisterTaskDefinition` stores a revision under its family. Revisions number from one and go up by
33
+ one with each registration.
34
+
35
+ ```typescript sim-ecs-register-task-definition
36
+ /**
37
+ * Registering a simulated task definition and reading it back.
38
+ */
39
+
40
+ import {
41
+ DescribeTaskDefinitionCommand,
42
+ RegisterTaskDefinitionCommand,
43
+ } from "@aws-sdk/client-ecs";
44
+
45
+ import { SimAws } from "@kensio/yulin";
46
+
47
+ const simAws = new SimAws();
48
+ const ecs = simAws.ecs();
49
+
50
+ await ecs.registerTaskDefinition(
51
+ new RegisterTaskDefinitionCommand({
52
+ family: "checkout",
53
+ cpu: "512",
54
+ memory: "1024",
55
+ networkMode: "awsvpc",
56
+ requiresCompatibilities: ["FARGATE"],
57
+ containerDefinitions: [
58
+ {
59
+ name: "app",
60
+ image: "example.dkr.ecr.eu-west-2.amazonaws.com/checkout:1",
61
+ essential: true,
62
+ portMappings: [{ containerPort: 8080, protocol: "tcp" }],
63
+ environment: [{ name: "LOG_LEVEL", value: "debug" }],
64
+ },
65
+ ],
66
+ }),
67
+ );
68
+
69
+ const described = await ecs.describeTaskDefinition(
70
+ new DescribeTaskDefinitionCommand({ taskDefinition: "checkout" }),
71
+ );
72
+
73
+ console.log(described.taskDefinition?.revision); // 1
74
+ console.log(described.taskDefinition?.status); // "ACTIVE"
75
+ console.log(described.taskDefinition?.containerDefinitions?.[0]?.image);
76
+ // "example.dkr.ecr.eu-west-2.amazonaws.com/checkout:1"
77
+ ```
78
+
79
+ Container definitions are stored as declared, whatever the image is and whether or not Yulin could
80
+ ever run it. That covers port mappings, environment variables and secrets. A `valueFrom` naming a
81
+ Secrets Manager secret is held as the identifier it is, and never resolved.
82
+
83
+ A registration that declares something this simulation has no room for is refused outright, and
84
+ never trimmed. That way a declaration cannot go missing from the revision it made.
85
+
86
+ ## Revisions
87
+
88
+ Each registration of a family takes the next revision number. Naming the family alone means its
89
+ latest active revision, which follows registrations as they are made.
90
+
91
+ ```typescript sim-ecs-task-definition-revisions
92
+ /**
93
+ * Registering a second revision of a simulated task definition family.
94
+ */
95
+
96
+ import {
97
+ DescribeTaskDefinitionCommand,
98
+ RegisterTaskDefinitionCommand,
99
+ } from "@aws-sdk/client-ecs";
100
+
101
+ import { SimAws } from "@kensio/yulin";
102
+
103
+ const simAws = new SimAws();
104
+ const ecs = simAws.ecs();
105
+
106
+ for (const tag of ["checkout:1", "checkout:2"]) {
107
+ await ecs.registerTaskDefinition(
108
+ new RegisterTaskDefinitionCommand({
109
+ family: "checkout",
110
+ containerDefinitions: [{ name: "app", image: tag }],
111
+ }),
112
+ );
113
+ }
114
+
115
+ const latest = await ecs.describeTaskDefinition(
116
+ new DescribeTaskDefinitionCommand({ taskDefinition: "checkout" }),
117
+ );
118
+
119
+ console.log(latest.taskDefinition?.revision); // 2
120
+
121
+ const first = await ecs.describeTaskDefinition(
122
+ new DescribeTaskDefinitionCommand({ taskDefinition: "checkout:1" }),
123
+ );
124
+
125
+ console.log(first.taskDefinition?.containerDefinitions?.[0]?.image);
126
+ // "checkout:1"
127
+ ```
128
+
129
+ `DescribeTaskDefinition` takes all three forms ECS takes, namely a family, a `family:revision`, and
130
+ a full task definition ARN. The ARN of a revision is
131
+ `arn:aws:ecs:<region>:<account>:task-definition/<family>:<revision>`.
132
+
133
+ ## Deregistering a revision
134
+
135
+ `DeregisterTaskDefinition` marks one revision `INACTIVE` without removing it. It stays describable
136
+ by `family:revision` and by ARN, because something already holding either of those still needs to
137
+ find out what it declared. What it stops being is the revision the family resolves to.
138
+
139
+ ```typescript sim-ecs-deregister-task-definition
140
+ /**
141
+ * Deregistering a simulated task definition revision.
142
+ */
143
+
144
+ import {
145
+ DeregisterTaskDefinitionCommand,
146
+ DescribeTaskDefinitionCommand,
147
+ RegisterTaskDefinitionCommand,
148
+ } from "@aws-sdk/client-ecs";
149
+
150
+ import { SimAws } from "@kensio/yulin";
151
+
152
+ const simAws = new SimAws();
153
+ const ecs = simAws.ecs();
154
+
155
+ for (const tag of ["checkout:1", "checkout:2"]) {
156
+ await ecs.registerTaskDefinition(
157
+ new RegisterTaskDefinitionCommand({
158
+ family: "checkout",
159
+ containerDefinitions: [{ name: "app", image: tag }],
160
+ }),
161
+ );
162
+ }
163
+
164
+ await ecs.deregisterTaskDefinition(
165
+ new DeregisterTaskDefinitionCommand({ taskDefinition: "checkout:2" }),
166
+ );
167
+
168
+ const deregistered = await ecs.describeTaskDefinition(
169
+ new DescribeTaskDefinitionCommand({ taskDefinition: "checkout:2" }),
170
+ );
171
+
172
+ console.log(deregistered.taskDefinition?.status); // "INACTIVE"
173
+
174
+ const latest = await ecs.describeTaskDefinition(
175
+ new DescribeTaskDefinitionCommand({ taskDefinition: "checkout" }),
176
+ );
177
+
178
+ console.log(latest.taskDefinition?.revision); // 1
179
+ ```
180
+
181
+ Deregistering must name one revision, as it must on real ECS. A family on its own is refused.
182
+
183
+ Revision numbers are never reused. Registering the family again after deregistering revision 2 gives
184
+ revision 3.
185
+
186
+ ## Listing task definitions
187
+
188
+ `ListTaskDefinitions` reports revision ARNs and `ListTaskDefinitionFamilies` reports family names.
189
+ Both take a `familyPrefix`. A `ListTaskDefinitions` request silent on status gets the active
190
+ revisions, while a `ListTaskDefinitionFamilies` request silent on it gets both the active and the
191
+ inactive families. That is how real ECS defaults each of them.
192
+
193
+ ```typescript sim-ecs-list-task-definitions
194
+ /**
195
+ * Listing simulated task definition revisions and families.
196
+ */
197
+
198
+ import {
199
+ ListTaskDefinitionFamiliesCommand,
200
+ ListTaskDefinitionsCommand,
201
+ RegisterTaskDefinitionCommand,
202
+ } from "@aws-sdk/client-ecs";
203
+
204
+ import { SimAws } from "@kensio/yulin";
205
+
206
+ const simAws = new SimAws();
207
+ const ecs = simAws.ecs();
208
+
209
+ for (const family of ["checkout", "billing"]) {
210
+ await ecs.registerTaskDefinition(
211
+ new RegisterTaskDefinitionCommand({
212
+ family,
213
+ containerDefinitions: [{ name: "app", image: `${family}:1` }],
214
+ }),
215
+ );
216
+ }
217
+
218
+ const revisions = await ecs.listTaskDefinitions(
219
+ new ListTaskDefinitionsCommand({ familyPrefix: "check" }),
220
+ );
221
+
222
+ console.log(revisions.taskDefinitionArns?.length); // 1
223
+
224
+ const families = await ecs.listTaskDefinitionFamilies(
225
+ new ListTaskDefinitionFamiliesCommand({}),
226
+ );
227
+
228
+ console.log(families.families); // ["checkout", "billing"]
229
+ ```
230
+
231
+ A family counts as inactive once every one of its revisions has been deregistered.
232
+ `ListTaskDefinitionFamilies` takes `ACTIVE`, `INACTIVE` and `ALL`, and `ListTaskDefinitions` takes
233
+ `ACTIVE` and `INACTIVE` along with a `sort` of `ASC` or `DESC`.
234
+
235
+ ## Clusters
236
+
237
+ A cluster is a named scope for the tasks and services that will run in it. `CreateCluster`,
238
+ `DescribeClusters`, `ListClusters` and `DeleteCluster` hold them.
239
+
240
+ ```typescript sim-ecs-clusters
241
+ /**
242
+ * Creating and describing a simulated ECS cluster.
243
+ */
244
+
245
+ import {
246
+ CreateClusterCommand,
247
+ DescribeClustersCommand,
248
+ ListClustersCommand,
249
+ } from "@aws-sdk/client-ecs";
250
+
251
+ import { SimAws } from "@kensio/yulin";
252
+
253
+ const simAws = new SimAws();
254
+ const ecs = simAws.ecs();
255
+
256
+ await ecs.createCluster(
257
+ new CreateClusterCommand({
258
+ clusterName: "services",
259
+ settings: [{ name: "containerInsights", value: "enabled" }],
260
+ tags: [{ key: "team", value: "platform" }],
261
+ }),
262
+ );
263
+
264
+ const described = await ecs.describeClusters(
265
+ new DescribeClustersCommand({
266
+ clusters: ["services"],
267
+ include: ["SETTINGS", "TAGS"],
268
+ }),
269
+ );
270
+
271
+ console.log(described.clusters?.[0]?.status); // "ACTIVE"
272
+ console.log(described.clusters?.[0]?.runningTasksCount); // 0
273
+ console.log(described.clusters?.[0]?.tags?.[0]?.key); // "team"
274
+
275
+ const listed = await ecs.listClusters(new ListClustersCommand({}));
276
+
277
+ console.log(listed.clusterArns?.[0]);
278
+ // "arn:aws:ecs:us-east-1:888888888888:cluster/services"
279
+ ```
280
+
281
+ Settings, configuration and tags are reported only where the request asked for them by name, as they
282
+ are on real ECS. A request naming no cluster means the `default` cluster.
283
+
284
+ `DeleteCluster` marks a cluster `INACTIVE`. It stays describable and drops out of `ListClusters`,
285
+ and creating a cluster of the same name again makes a new active one, listed in the position it was
286
+ created in. Unlike the operations that read a cluster, `DeleteCluster` needs one to be named. A
287
+ request naming none is refused, and never read as meaning the `default` cluster.
288
+
289
+ A cluster is named either by its short name or by its full ARN, and the two are interchangeable. An
290
+ ARN belonging to another account or region names a different cluster. `DescribeClusters` reports it
291
+ as a `MISSING` failure, and `DeleteCluster` refuses it.
292
+
293
+ A cluster has to exist before a task can run in it, including the `default` one. Yulin creates no
294
+ cluster on its own, so create the one the tasks run in.
295
+
296
+ ## Running a task
297
+
298
+ `bindContainer` says what a container runs. `RunTask` then starts a task in a cluster, and the bound
299
+ handlers run in this process.
300
+
301
+ ```typescript sim-ecs-run-task
302
+ /**
303
+ * Running a simulated ECS task from a bound container handler.
304
+ */
305
+
306
+ import {
307
+ CreateClusterCommand,
308
+ DescribeTasksCommand,
309
+ RegisterTaskDefinitionCommand,
310
+ RunTaskCommand,
311
+ } from "@aws-sdk/client-ecs";
312
+
313
+ import { SimAws } from "@kensio/yulin";
314
+
315
+ const simAws = new SimAws();
316
+ const ecs = simAws.ecs();
317
+
318
+ await ecs.createCluster(new CreateClusterCommand({ clusterName: "orders" }));
319
+
320
+ const processed: string[] = [];
321
+
322
+ ecs.bindContainer({
323
+ family: "orders-worker",
324
+ containerName: "app",
325
+ run: async () => {
326
+ await Promise.resolve();
327
+ processed.push("outstanding orders");
328
+ },
329
+ });
330
+
331
+ await ecs.registerTaskDefinition(
332
+ new RegisterTaskDefinitionCommand({
333
+ family: "orders-worker",
334
+ containerDefinitions: [
335
+ { name: "app", image: "orders-worker:1" },
336
+ { name: "log-router", image: "aws-for-fluent-bit:latest" },
337
+ ],
338
+ }),
339
+ );
340
+
341
+ const run = await ecs.runTask(
342
+ new RunTaskCommand({ cluster: "orders", taskDefinition: "orders-worker" }),
343
+ );
344
+
345
+ console.log(run.tasks?.[0]?.lastStatus); // "PROVISIONING"
346
+
347
+ // The containers run in the background, as they do on real ECS.
348
+ await simAws.backgroundTasksComplete();
349
+
350
+ console.log(processed); // ["outstanding orders"]
351
+
352
+ const described = await ecs.describeTasks(
353
+ new DescribeTasksCommand({
354
+ cluster: "orders",
355
+ tasks: [run.tasks?.[0]?.taskArn ?? ""],
356
+ }),
357
+ );
358
+
359
+ console.log(described.tasks?.[0]?.lastStatus); // "STOPPED"
360
+ console.log(described.tasks?.[0]?.containers?.[0]?.exitCode); // 0
361
+ console.log(described.tasks?.[0]?.containers?.[1]?.reason);
362
+ // "Not simulated: no executable binding matches this container, ..."
363
+ ```
364
+
365
+ `RunTask` answers before the containers have run, with the task in `PROVISIONING`, as real ECS
366
+ answers it. Waiting for the simulator's background work is what runs them.
367
+
368
+ The log router in that task definition has no binding. It never starts, and says why. That is the
369
+ ordinary shape of a real task definition, with an application container Yulin can run next to
370
+ containers it never could.
371
+
372
+ A handler that throws stops its container with an exit code of 1 and the error message as its
373
+ reason. The `RunTask` call itself still succeeds, because a real container fails with nobody
374
+ watching the call either. A task definition where nothing at all is bound still creates the task.
375
+ The task stops with a `stopCode` of `TaskFailedToStart` saying that nothing ran.
376
+
377
+ ### Binding by image repository
378
+
379
+ A container built by CDK or by a pipeline has an image tag that changes with every build, so naming
380
+ the container by hand is the wrong way round. A binding can name the repository instead, and the tag
381
+ is ignored on both sides.
382
+
383
+ ```typescript sim-ecs-bind-image-repository
384
+ /**
385
+ * Binding a simulated ECS container by the repository its image comes from.
386
+ */
387
+
388
+ import {
389
+ CreateClusterCommand,
390
+ RegisterTaskDefinitionCommand,
391
+ RunTaskCommand,
392
+ } from "@aws-sdk/client-ecs";
393
+
394
+ import { SimAws } from "@kensio/yulin";
395
+
396
+ const simAws = new SimAws();
397
+ const ecs = simAws.ecs();
398
+
399
+ await ecs.createCluster(new CreateClusterCommand({}));
400
+
401
+ const started: string[] = [];
402
+
403
+ ecs.bindContainer({
404
+ imageRepository: "111111111111.dkr.ecr.eu-west-2.amazonaws.com/orders",
405
+ run: () => {
406
+ started.push("app");
407
+ },
408
+ });
409
+
410
+ await ecs.registerTaskDefinition(
411
+ new RegisterTaskDefinitionCommand({
412
+ family: "orders-worker",
413
+ containerDefinitions: [
414
+ {
415
+ name: "app",
416
+ image: "111111111111.dkr.ecr.eu-west-2.amazonaws.com/orders:8f2c1a9b",
417
+ },
418
+ ],
419
+ }),
420
+ );
421
+
422
+ await ecs.runTask(new RunTaskCommand({ taskDefinition: "orders-worker" }));
423
+ await simAws.backgroundTasksComplete();
424
+
425
+ console.log(started); // ["app"]
426
+ ```
427
+
428
+ The registry host is part of the repository, and a same-named repository in another account or
429
+ region misses. A binding naming the family and the container name beats one naming a repository
430
+ where both would match, and binding the same container again replaces what it runs.
431
+
432
+ The other shape a binding can take is `http`, a fetch-style `(request: Request) => Response |
433
+ Promise<Response>` for a service container behind a load balancer. It is called once for each
434
+ request routed to the container, and is covered under
435
+ [serving requests behind a load balancer](#serving-requests-behind-a-load-balancer).
436
+
437
+ ## What a container sees while it runs
438
+
439
+ The container definition's `environment` is visible through `process.env` for the length of the run,
440
+ along with any `RunTask` container override and the region variables a real task agent sets. This
441
+ works the same way it does for a sim Lambda function handler, through Node.js asynchronous context
442
+ tracking. The host process environment is underneath, and what the test process set stays readable.
443
+ Every container gets the region variables, whatever its definition declares, and an SDK client the
444
+ container builds reads `AWS_REGION` to find the region its task runs in.
445
+
446
+ ```typescript sim-ecs-run-task-environment
447
+ /**
448
+ * The environment variables a simulated ECS container runs with.
449
+ */
450
+
451
+ import {
452
+ CreateClusterCommand,
453
+ RegisterTaskDefinitionCommand,
454
+ RunTaskCommand,
455
+ } from "@aws-sdk/client-ecs";
456
+
457
+ import { SimAws } from "@kensio/yulin";
458
+
459
+ const simAws = new SimAws();
460
+ const ecs = simAws.ecs();
461
+
462
+ await ecs.createCluster(new CreateClusterCommand({}));
463
+
464
+ const batchSizes: (string | undefined)[] = [];
465
+
466
+ ecs.bindContainer({
467
+ family: "orders-worker",
468
+ containerName: "app",
469
+ run: () => {
470
+ batchSizes.push(process.env["BATCH_SIZE"]);
471
+ },
472
+ });
473
+
474
+ await ecs.registerTaskDefinition(
475
+ new RegisterTaskDefinitionCommand({
476
+ family: "orders-worker",
477
+ containerDefinitions: [
478
+ {
479
+ name: "app",
480
+ image: "orders-worker:1",
481
+ environment: [{ name: "BATCH_SIZE", value: "100" }],
482
+ },
483
+ ],
484
+ }),
485
+ );
486
+
487
+ await ecs.runTask(
488
+ new RunTaskCommand({
489
+ taskDefinition: "orders-worker",
490
+ overrides: {
491
+ containerOverrides: [
492
+ { name: "app", environment: [{ name: "BATCH_SIZE", value: "10" }] },
493
+ ],
494
+ },
495
+ }),
496
+ );
497
+ await simAws.backgroundTasksComplete();
498
+
499
+ console.log(batchSizes); // ["10"]
500
+ ```
501
+
502
+ A variable read at module scope, as in `const size = process.env.BATCH_SIZE` at the top of a file,
503
+ is read when the test imports that file and not when the container runs, and it sees the host value.
504
+ Read inside the handler to get the container's own.
505
+
506
+ ## The task role
507
+
508
+ While a container runs, its AWS calls are attributed to the task definition's `taskRoleArn`, in the
509
+ same way a sim Lambda function's are to its execution role. Calls made through an SDK client
510
+ intercepted by `SimSdk` pick this up without the code under test knowing.
511
+
512
+ ```typescript sim-ecs-task-role
513
+ /**
514
+ * Authorizing what a simulated ECS container does as the task Role.
515
+ */
516
+
517
+ import {
518
+ CreateClusterCommand,
519
+ RegisterTaskDefinitionCommand,
520
+ RunTaskCommand,
521
+ } from "@aws-sdk/client-ecs";
522
+ import { CreateRoleCommand, PutRolePolicyCommand } from "@aws-sdk/client-iam";
523
+ import { PutParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
524
+
525
+ import { SimSdk } from "@kensio/yulin/sdk";
526
+
527
+ using simSdk = new SimSdk();
528
+ const { simAws } = simSdk;
529
+ const ecs = simAws.ecs();
530
+ const accountId = simAws.defaultAccountId;
531
+
532
+ const taskRole = await simAws.iam().createRole(
533
+ new CreateRoleCommand({
534
+ RoleName: "OrdersTaskRole",
535
+ AssumeRolePolicyDocument: JSON.stringify({
536
+ Version: "2012-10-17",
537
+ Statement: {
538
+ Effect: "Allow",
539
+ Principal: { Service: "ecs-tasks.amazonaws.com" },
540
+ Action: "sts:AssumeRole",
541
+ },
542
+ }),
543
+ }),
544
+ );
545
+
546
+ await simAws.iam().putRolePolicy(
547
+ new PutRolePolicyCommand({
548
+ RoleName: "OrdersTaskRole",
549
+ PolicyName: "WriteLastRun",
550
+ PolicyDocument: JSON.stringify({
551
+ Version: "2012-10-17",
552
+ Statement: {
553
+ Effect: "Allow",
554
+ Action: "ssm:PutParameter",
555
+ Resource: `arn:aws:ssm:${simAws.defaultRegionName}:${accountId}:parameter/orders/last-run`,
556
+ },
557
+ }),
558
+ }),
559
+ );
560
+
561
+ await ecs.createCluster(new CreateClusterCommand({}));
562
+
563
+ simSdk.intercept(SSMClient);
564
+
565
+ ecs.bindContainer({
566
+ family: "orders-worker",
567
+ containerName: "app",
568
+ run: async () => {
569
+ await new SSMClient({}).send(
570
+ new PutParameterCommand({
571
+ Name: "/orders/last-run",
572
+ Value: "done",
573
+ Type: "String",
574
+ }),
575
+ );
576
+ },
577
+ });
578
+
579
+ await ecs.registerTaskDefinition(
580
+ new RegisterTaskDefinitionCommand({
581
+ family: "orders-worker",
582
+ taskRoleArn: taskRole.Role.Arn,
583
+ containerDefinitions: [{ name: "app", image: "orders-worker:1" }],
584
+ }),
585
+ );
586
+
587
+ await ecs.runTask(new RunTaskCommand({ taskDefinition: "orders-worker" }));
588
+ await simAws.backgroundTasksComplete();
589
+ ```
590
+
591
+ Take the policy away and the same run stops the container with an exit code of 1, carrying the IAM
592
+ denial as its reason. That is the test worth writing. A task role missing a permission fails in the
593
+ test, ahead of the deployment.
594
+
595
+ A `RunTask` request can override the role with `overrides.taskRoleArn`. A task definition declaring
596
+ no task role runs its containers as nobody, and their AWS calls are denied. A real task without one
597
+ has no credentials of its own, and taking the identity of whoever called `RunTask` would let a test
598
+ pass on permissions the deployed task lacks. The execution role covers a container's `secrets`
599
+ alone, since there is no image to pull and no log driver to write to.
600
+
601
+ ## Container secrets
602
+
603
+ A container definition's `secrets` are resolved when the task starts, from simulated Secrets Manager
604
+ or simulated SSM Parameter Store according to what each `valueFrom` names, and the values appear in
605
+ the container's environment alongside its declared `environment`. A handler reads them through
606
+ `process.env` like anything else.
607
+
608
+ They are read as the task definition's `executionRoleArn` rather than its `taskRoleArn`. That is the
609
+ split real ECS makes. The execution role is what the task agent pulls secrets with before a
610
+ container starts, and the task role is what the running container's own AWS calls are attributed to.
611
+ A role allowed one is not thereby allowed the other.
612
+
613
+ ```typescript sim-ecs-container-secrets
614
+ /**
615
+ * Resolving a simulated ECS container's secrets as the execution Role.
616
+ */
617
+
618
+ import {
619
+ CreateClusterCommand,
620
+ RegisterTaskDefinitionCommand,
621
+ RunTaskCommand,
622
+ } from "@aws-sdk/client-ecs";
623
+ import { CreateRoleCommand, PutRolePolicyCommand } from "@aws-sdk/client-iam";
624
+ import { CreateSecretCommand } from "@aws-sdk/client-secrets-manager";
625
+
626
+ import { SimAws } from "@kensio/yulin";
627
+
628
+ const simAws = new SimAws();
629
+ const ecs = simAws.ecs();
630
+
631
+ const secret = await simAws.secretsManager().createSecret(
632
+ new CreateSecretCommand({
633
+ Name: "orders/db",
634
+ SecretString: JSON.stringify({ username: "orders", password: "s3cr3t" }),
635
+ }),
636
+ );
637
+
638
+ const executionRole = await simAws.iam().createRole(
639
+ new CreateRoleCommand({
640
+ RoleName: "OrdersExecutionRole",
641
+ AssumeRolePolicyDocument: JSON.stringify({
642
+ Version: "2012-10-17",
643
+ Statement: {
644
+ Effect: "Allow",
645
+ Principal: { Service: "ecs-tasks.amazonaws.com" },
646
+ Action: "sts:AssumeRole",
647
+ },
648
+ }),
649
+ }),
650
+ );
651
+
652
+ await simAws.iam().putRolePolicy(
653
+ new PutRolePolicyCommand({
654
+ RoleName: "OrdersExecutionRole",
655
+ PolicyName: "ReadOrdersDbSecret",
656
+ PolicyDocument: JSON.stringify({
657
+ Version: "2012-10-17",
658
+ Statement: {
659
+ Effect: "Allow",
660
+ Action: "secretsmanager:GetSecretValue",
661
+ Resource: secret.ARN,
662
+ },
663
+ }),
664
+ }),
665
+ );
666
+
667
+ await ecs.createCluster(new CreateClusterCommand({}));
668
+
669
+ const passwords: (string | undefined)[] = [];
670
+
671
+ ecs.bindContainer({
672
+ family: "orders-worker",
673
+ containerName: "app",
674
+ run: () => {
675
+ passwords.push(process.env["DB_PASSWORD"]);
676
+ },
677
+ });
678
+
679
+ await ecs.registerTaskDefinition(
680
+ new RegisterTaskDefinitionCommand({
681
+ family: "orders-worker",
682
+ executionRoleArn: executionRole.Role.Arn,
683
+ containerDefinitions: [
684
+ {
685
+ name: "app",
686
+ image: "orders-worker:1",
687
+ secrets: [
688
+ {
689
+ name: "DB_PASSWORD",
690
+ valueFrom: `${String(secret.ARN)}:password::`,
691
+ },
692
+ ],
693
+ },
694
+ ],
695
+ }),
696
+ );
697
+
698
+ await ecs.runTask(new RunTaskCommand({ taskDefinition: "orders-worker" }));
699
+ await simAws.backgroundTasksComplete();
700
+
701
+ console.log(passwords); // ["s3cr3t"]
702
+ ```
703
+
704
+ A `valueFrom` may be a Secrets Manager ARN, an SSM parameter ARN, or a bare parameter name, which
705
+ real ECS accepts for a parameter in the task's own region. A Secrets Manager ARN may carry a JSON
706
+ key, a version stage and a version id after the secret id, in the form
707
+ `...:secret:orders/db-AbCdEf:password::` that a CDK construct given a field writes. The key selects
708
+ one field of a secret holding a JSON object. A `SecureString` parameter is decrypted, as it is for a
709
+ real task. The decryption is made with the execution role's own permissions, and that role needs
710
+ `kms:Decrypt` on top of `ssm:GetParameter`.
711
+
712
+ A secret that cannot be resolved stops the task before any container runs, with a
713
+ `ResourceInitializationError` reason naming the variable:
714
+
715
+ ```text
716
+ ResourceInitializationError: unable to pull secrets: DB_PASSWORD: User:
717
+ arn:aws:iam::111111111111:role/OrdersExecutionRole is not authorized to perform:
718
+ secretsmanager:GetSecretValue on resource: arn:aws:secretsmanager:...
719
+ ```
720
+
721
+ The task stops with `TaskFailedToStart`, never reaches `RUNNING`, and the bound handler stays unrun.
722
+ A secret that was never created, a task definition declaring secrets with no `executionRoleArn`, and
723
+ a JSON key the secret lacks each stop it the same way with their own reason.
724
+
725
+ ## Running a task from a rule or a schedule
726
+
727
+ A `RunTask` call is one way to start a task, and there are two others. A
728
+ [simulated EventBridge](https://yulinsim.dev/services/eventbridge/) rule target and a [simulated Scheduler](https://yulinsim.dev/services/scheduler/)
729
+ schedule target can both name an ECS cluster, and both then run a task here when the rule matches an
730
+ event or the schedule falls due. That is the usual shape of a nightly batch job or an import kicked
731
+ off by something happening.
732
+
733
+ Both go through `RunTask`, and a task started that way is the same task as one started by a caller.
734
+ That means the same cluster and revision lookups, the same IAM decision against `ecs:RunTask`, and
735
+ the same task state afterwards. What a target may ask for differs, though. `EcsParameters` takes and
736
+ ignores the launch type, platform version, network configuration and capacity provider strategy that
737
+ `RunTask` refuses, since a target written for real AWS carries them and refusing one would make an
738
+ otherwise workable target unusable. The other difference is who runs it. A rule or a schedule runs
739
+ the task as the role on its target, and that role needs `ecs:RunTask` on the revision. The task role
740
+ inside the task definition is still what the containers' own AWS calls are attributed to.
741
+
742
+ The container model applies unchanged. Only a bound container runs, and a target naming a task
743
+ definition with nothing bound records a task that never started, leaving the rule or the schedule
744
+ itself successful. Writing one of these targets is documented where the target is written, in
745
+ [running an ECS task](https://yulinsim.dev/services/eventbridge/#running-an-ecs-task) for a rule and
746
+ [running an ECS task on a schedule](https://yulinsim.dev/services/scheduler/#running-an-ecs-task-on-a-schedule) for a schedule.
747
+
748
+ ## Describing, listing and stopping tasks
749
+
750
+ `DescribeTasks` reports a task by its id or its full ARN, with its containers, which of them ran and
751
+ their exit codes. A task it cannot find comes back as a `MISSING` failure entry, and never as an
752
+ error.
753
+
754
+ `ListTasks` filters on a desired status of `RUNNING` when a request says nothing, as real ECS does.
755
+ A task that has finished is only listed by asking for the stopped ones with `desiredStatus:
756
+ "STOPPED"`. It also filters by `family`, `startedBy` and `launchType`.
757
+
758
+ `StopTask` sets the desired status to `STOPPED` and records the reason. A task whose containers have
759
+ yet to start runs none of them, and one stopped part way through runs no more. A test can therefore
760
+ stop a task between `RunTask` and the background work that runs it. A task that has already stopped
761
+ is reported as it stands, keeping the reason it stopped for.
762
+
763
+ ## Services
764
+
765
+ A task runs and stops. A service keeps tasks running, and that is what a deployed application
766
+ usually is. It is a named service in a cluster, running some number of tasks from a task definition.
767
+
768
+ `CreateService` creates one. Its tasks exist as soon as the request is answered and reach `RUNNING`
769
+ on the simulation's background work, as real ECS brings a new service up. The service reports the
770
+ desired count it was given, and a running count that catches up.
771
+
772
+ ```typescript sim-ecs-create-service
773
+ /**
774
+ * Creating a simulated ECS service that keeps three tasks running.
775
+ */
776
+
777
+ import {
778
+ CreateClusterCommand,
779
+ CreateServiceCommand,
780
+ DescribeServicesCommand,
781
+ ListTasksCommand,
782
+ RegisterTaskDefinitionCommand,
783
+ } from "@aws-sdk/client-ecs";
784
+
785
+ import { SimAws } from "@kensio/yulin";
786
+
787
+ const simAws = new SimAws();
788
+ const ecs = simAws.ecs();
789
+
790
+ await ecs.createCluster(new CreateClusterCommand({ clusterName: "orders" }));
791
+
792
+ ecs.bindContainer({
793
+ family: "checkout",
794
+ containerName: "app",
795
+ run: async () => {
796
+ await handleOneRequest();
797
+ },
798
+ });
799
+
800
+ await ecs.registerTaskDefinition(
801
+ new RegisterTaskDefinitionCommand({
802
+ family: "checkout",
803
+ containerDefinitions: [{ name: "app", image: "checkout:1" }],
804
+ }),
805
+ );
806
+
807
+ const created = await ecs.createService(
808
+ new CreateServiceCommand({
809
+ cluster: "orders",
810
+ serviceName: "checkout",
811
+ taskDefinition: "checkout",
812
+ desiredCount: 3,
813
+ }),
814
+ );
815
+
816
+ console.log(created.service?.desiredCount); // 3
817
+ console.log(created.service?.runningCount); // 0, as real ECS answers one
818
+
819
+ // The tasks come up in the background, as they do on real ECS.
820
+ await simAws.backgroundTasksComplete();
821
+
822
+ const described = await ecs.describeServices(
823
+ new DescribeServicesCommand({ cluster: "orders", services: ["checkout"] }),
824
+ );
825
+
826
+ console.log(described.services?.[0]?.runningCount); // 3
827
+
828
+ const listed = await ecs.listTasks(
829
+ new ListTasksCommand({ cluster: "orders", serviceName: "checkout" }),
830
+ );
831
+
832
+ console.log(listed.taskArns?.length); // 3
833
+
834
+ async function handleOneRequest(): Promise<void> {
835
+ await Promise.resolve();
836
+ }
837
+ ```
838
+
839
+ Each of those tasks is a task like any other. `ListTasks` returns its ARN, `DescribeTasks` describes
840
+ it, and its containers report which of them Yulin is simulating.
841
+
842
+ ### What the desired count models
843
+
844
+ A desired count of three does not mean three copies of your handler. Yulin runs in one Node.js
845
+ process and there are no containers to copy, so the count is simulated as state. Three tasks exist
846
+ and are reported as running, while the handler bound to a container is called once per request or
847
+ per poll, whenever something reaches it.
848
+
849
+ That is a deliberate divergence. In practice a service is worth asserting on for its state (how many
850
+ tasks it keeps, which revision they run) and not for anything about running three things at once. A
851
+ test that needs concurrency is a test about your own code.
852
+
853
+ A container of a service that has come up is treated as running from that point, and what calls it
854
+ is whatever reaches it, either a queue it consumes or a load balancer sending it a request. Both are
855
+ covered below.
856
+
857
+ ### Consuming a queue
858
+
859
+ A worker container reads an SQS queue in a loop. It receives a batch, handles it, deletes it, and
860
+ goes round again. A binding cannot do that. An endless loop in a single Node.js process blocks
861
+ everything and never yields to the test running it, leaving the test no chance to assert on what the
862
+ loop did.
863
+
864
+ So Yulin runs the loop and the binding supplies its body. A container declares `consumes` in place
865
+ of `run`, naming the queue and a handler for a batch of messages, and Yulin receives, hands the
866
+ batch over, and deletes it when the handler returns. The bound thing is the body of the loop, and
867
+ not the loop itself. That is the one divergence worth keeping in mind here.
868
+
869
+ ```typescript sim-ecs-consume-queue
870
+ /**
871
+ * A simulated ECS service whose container consumes an SQS queue.
872
+ */
873
+
874
+ import {
875
+ CreateClusterCommand,
876
+ CreateServiceCommand,
877
+ RegisterTaskDefinitionCommand,
878
+ } from "@aws-sdk/client-ecs";
879
+ import { CreateRoleCommand, PutRolePolicyCommand } from "@aws-sdk/client-iam";
880
+ import { CreateQueueCommand, SendMessageCommand } from "@aws-sdk/client-sqs";
881
+
882
+ import { SimAws } from "@kensio/yulin";
883
+
884
+ const simAws = new SimAws();
885
+ const ecs = simAws.ecs();
886
+ const sqs = simAws.sqs();
887
+ const queueArn = `arn:aws:sqs:${simAws.defaultRegionName}:${simAws.defaultAccountId}:orders`;
888
+
889
+ const queue = await sqs.createQueue(
890
+ new CreateQueueCommand({ QueueName: "orders" }),
891
+ );
892
+ const queueUrl = queue.QueueUrl ?? "";
893
+
894
+ const taskRole = await simAws.iam().createRole(
895
+ new CreateRoleCommand({
896
+ RoleName: "OrdersWorkerTaskRole",
897
+ AssumeRolePolicyDocument: JSON.stringify({
898
+ Version: "2012-10-17",
899
+ Statement: {
900
+ Effect: "Allow",
901
+ Principal: { Service: "ecs-tasks.amazonaws.com" },
902
+ Action: "sts:AssumeRole",
903
+ },
904
+ }),
905
+ }),
906
+ );
907
+
908
+ await simAws.iam().putRolePolicy(
909
+ new PutRolePolicyCommand({
910
+ RoleName: "OrdersWorkerTaskRole",
911
+ PolicyName: "ConsumeOrders",
912
+ PolicyDocument: JSON.stringify({
913
+ Version: "2012-10-17",
914
+ Statement: {
915
+ Effect: "Allow",
916
+ Action: [
917
+ "sqs:ReceiveMessage",
918
+ "sqs:DeleteMessage",
919
+ "sqs:GetQueueAttributes",
920
+ ],
921
+ Resource: queueArn,
922
+ },
923
+ }),
924
+ }),
925
+ );
926
+
927
+ const handled: string[] = [];
928
+
929
+ ecs.bindContainer({
930
+ family: "orders-worker",
931
+ containerName: "app",
932
+ consumes: {
933
+ queueUrl,
934
+ batchSize: 10,
935
+ handler: (messages) => {
936
+ handled.push(...messages.map((message) => message.Body));
937
+ },
938
+ },
939
+ });
940
+
941
+ await ecs.createCluster(new CreateClusterCommand({ clusterName: "orders" }));
942
+ await ecs.registerTaskDefinition(
943
+ new RegisterTaskDefinitionCommand({
944
+ family: "orders-worker",
945
+ taskRoleArn: taskRole.Role.Arn,
946
+ containerDefinitions: [{ name: "app", image: "orders-worker:1" }],
947
+ }),
948
+ );
949
+ await ecs.createService(
950
+ new CreateServiceCommand({
951
+ cluster: "orders",
952
+ serviceName: "orders-worker",
953
+ taskDefinition: "orders-worker",
954
+ desiredCount: 1,
955
+ }),
956
+ );
957
+
958
+ await sqs.sendMessage(
959
+ new SendMessageCommand({ QueueUrl: queueUrl, MessageBody: "order-1" }),
960
+ );
961
+ await simAws.backgroundTasksComplete();
962
+
963
+ console.log(handled); // ["order-1"]
964
+ ```
965
+
966
+ The queue is named by the URL `CreateQueue` answered with, and it has to be in the same account and
967
+ region as the service, as a real task's own queue is. A `batchSize` is how many messages the handler
968
+ is given at once, up to the ten one SQS receive hands out, and it defaults to ten. The handler may
969
+ be async, and is awaited. A binding declares one of `run`, `consumes` and `http`. Where a binding
970
+ carries more than one, `consumes` wins, then `http`, then `run`.
971
+
972
+ The task role is required here. That is the part worth noticing before writing the first one.
973
+ Polling is done as the task role, and a task definition with no `taskRoleArn` polls as nobody and
974
+ has its very first poll denied. That is what a real worker container with no credentials would hit.
975
+
976
+ #### Polling runs on the simulated clock
977
+
978
+ Yulin never polls in the background of a test. A message that can be received now is delivered as
979
+ soon as the simulation settles, and `await simAws.backgroundTasksComplete()` is enough for an
980
+ ordinary send. Anything that has to wait (a message sent with `DelaySeconds`, or a batch coming back
981
+ after its visibility timeout) waits on the simulated clock. Freezing time holds it, and advancing
982
+ time delivers it.
983
+
984
+ ```typescript sim-ecs-consume-queue-clock
985
+ /**
986
+ * Driving a simulated ECS container's polling with the simulated clock.
987
+ */
988
+
989
+ import { SendMessageCommand } from "@aws-sdk/client-sqs";
990
+
991
+ import type { SimAws } from "@kensio/yulin";
992
+
993
+ declare const simAws: SimAws;
994
+ declare const queueUrl: string;
995
+ declare const handled: string[];
996
+
997
+ simAws.clock().freeze();
998
+
999
+ await simAws.sqs().sendMessage(
1000
+ new SendMessageCommand({
1001
+ QueueUrl: queueUrl,
1002
+ MessageBody: "order-1",
1003
+ DelaySeconds: 60,
1004
+ }),
1005
+ );
1006
+ await simAws.backgroundTasksComplete();
1007
+
1008
+ console.log(handled.length); // 0, the message is not receivable yet
1009
+
1010
+ await simAws.clock().advanceBy({ seconds: 60 });
1011
+
1012
+ console.log(handled.length); // 1, the clock got there and the poll happened
1013
+ ```
1014
+
1015
+ #### What the handler returning and throwing mean
1016
+
1017
+ A handler that returns has handled the batch, and Yulin deletes it. A handler that throws leaves the
1018
+ whole batch on the queue. It stays hidden for the queue's visibility timeout and is handed over
1019
+ again when that runs out. That is what a real worker crashing part way through a batch does. A
1020
+ redrive policy therefore gives up on a message the handler keeps throwing on, exactly as it would
1021
+ for the deployed container.
1022
+
1023
+ The error goes no further than the container. What the sender sees is the message coming back.
1024
+
1025
+ #### Polling is authorized as the task role
1026
+
1027
+ Receiving, deleting and reading the queue's visibility timeout are all made as the task definition's
1028
+ `taskRoleArn`, and so is anything the handler itself does. A task role without `sqs:ReceiveMessage`,
1029
+ `sqs:DeleteMessage` or `sqs:GetQueueAttributes` on the queue is refused, and the refusal surfaces
1030
+ from `backgroundTasksComplete()`. That is the point of it. A policy that would break the deployed
1031
+ worker breaks the test.
1032
+
1033
+ A task definition with no `taskRoleArn` polls anonymously and is denied at its very first poll, as a
1034
+ real task with no credentials of its own would be.
1035
+
1036
+ #### Polling starts and stops with the service
1037
+
1038
+ Polling starts when the service's first task comes up and stops when the service is deleted, scaled
1039
+ to zero, or its `SimAws` is closed. A stopped poller releases both the queue watch and the clock
1040
+ wait. A test that finishes with a consuming service leaves nothing behind it.
1041
+
1042
+ There is one poller per service and container, and never one per task. A desired count of three is
1043
+ three simulated tasks reported as running, and the handler is still called once per poll, which is
1044
+ the same divergence the desired count already rests on. Three real containers would each run their
1045
+ own loop and share the queue between them, coming to the same messages being handled once.
1046
+
1047
+ A consuming container is the one kind a `RunTask` task cannot run. It has no handler that ends, and
1048
+ a task has to end. A task started from the same definition records the container as not simulated,
1049
+ with a reason saying to create a service instead.
1050
+
1051
+ ### Serving requests behind a load balancer
1052
+
1053
+ A service container that answers HTTP requests declares `http` in place of `run` or `consumes`. The
1054
+ handler is fetch-style. It is given a `Request` and answers with a `Response`, the same shape a
1055
+ simulated load balancer already answers a served request with.
1056
+
1057
+ What reaches it is a request routed through simulated [Elastic Load Balancing](https://yulinsim.dev/services/elbv2/). A service
1058
+ declares `loadBalancers` naming a target group, a container and a container port. Creating the
1059
+ service registers each of its tasks into that target group, and a request the load balancer forwards
1060
+ there reaches the bound container's handler.
1061
+
1062
+ ```typescript sim-ecs-serve-load-balancer
1063
+ /**
1064
+ * A simulated ECS service answering requests behind a load balancer.
1065
+ */
1066
+
1067
+ import {
1068
+ CreateClusterCommand,
1069
+ CreateServiceCommand,
1070
+ RegisterTaskDefinitionCommand,
1071
+ } from "@aws-sdk/client-ecs";
1072
+ import {
1073
+ CreateListenerCommand,
1074
+ CreateLoadBalancerCommand,
1075
+ CreateTargetGroupCommand,
1076
+ } from "@aws-sdk/client-elastic-load-balancing-v2";
1077
+
1078
+ import { SimAws } from "@kensio/yulin";
1079
+ import { simElbV2Fetch } from "@kensio/yulin/elbv2";
1080
+
1081
+ const simAws = new SimAws();
1082
+ const ecs = simAws.ecs();
1083
+ const elbV2 = simAws.elbV2();
1084
+
1085
+ const targetGroup = await elbV2.createTargetGroup(
1086
+ new CreateTargetGroupCommand({
1087
+ Name: "orders-tg",
1088
+ TargetType: "ip",
1089
+ Protocol: "HTTP",
1090
+ Port: 8080,
1091
+ }),
1092
+ );
1093
+
1094
+ const targetGroupArn = targetGroup.TargetGroups?.[0]?.TargetGroupArn;
1095
+
1096
+ const loadBalancer = await elbV2.createLoadBalancer(
1097
+ new CreateLoadBalancerCommand({ Name: "orders-alb" }),
1098
+ );
1099
+
1100
+ await elbV2.createListener(
1101
+ new CreateListenerCommand({
1102
+ LoadBalancerArn: loadBalancer.LoadBalancers?.[0]?.LoadBalancerArn,
1103
+ Protocol: "HTTP",
1104
+ Port: 80,
1105
+ DefaultActions: [{ Type: "forward", TargetGroupArn: targetGroupArn }],
1106
+ }),
1107
+ );
1108
+
1109
+ await ecs.createCluster(new CreateClusterCommand({ clusterName: "orders" }));
1110
+
1111
+ ecs.bindContainer({
1112
+ family: "orders-api",
1113
+ containerName: "app",
1114
+ http: (request) => {
1115
+ const { pathname } = new URL(request.url);
1116
+
1117
+ return Response.json({ path: pathname }, { status: 200 });
1118
+ },
1119
+ });
1120
+
1121
+ await ecs.registerTaskDefinition(
1122
+ new RegisterTaskDefinitionCommand({
1123
+ family: "orders-api",
1124
+ containerDefinitions: [
1125
+ {
1126
+ name: "app",
1127
+ image: "orders-api:1",
1128
+ portMappings: [{ containerPort: 8080 }],
1129
+ },
1130
+ ],
1131
+ }),
1132
+ );
1133
+
1134
+ await ecs.createService(
1135
+ new CreateServiceCommand({
1136
+ cluster: "orders",
1137
+ serviceName: "orders-api",
1138
+ taskDefinition: "orders-api",
1139
+ desiredCount: 2,
1140
+ loadBalancers: [
1141
+ { targetGroupArn, containerName: "app", containerPort: 8080 },
1142
+ ],
1143
+ }),
1144
+ );
1145
+
1146
+ // The tasks come up in the background, as they do on real ECS, and each of
1147
+ // them is registered in the target group as it starts.
1148
+ await simAws.backgroundTasksComplete();
1149
+
1150
+ const dnsName = loadBalancer.LoadBalancers?.[0]?.DNSName;
1151
+ const response = await simElbV2Fetch(simAws, `http://${dnsName}/orders/42`);
1152
+
1153
+ console.log(response.status); // 200
1154
+ console.log(await response.json()); // { path: "/orders/42" }
1155
+ ```
1156
+
1157
+ The handler runs as the task role, with the container's environment applied, exactly as a container
1158
+ consuming a queue or one run by `RunTask` does. So application code inside it builds an SDK client
1159
+ from `process.env` and is authorized by simulated IAM against the role the task definition declared.
1160
+
1161
+ The container sees the request the client made, with the headers a load balancer writes in front of
1162
+ a target. Those are the `host` the client asked for, `x-forwarded-for`, `x-forwarded-proto`,
1163
+ `x-forwarded-port` and `x-amzn-trace-id`. The URL is the AWS-facing one, and `new URL(request.url)`
1164
+ reads the name the client asked for and never a localhost one.
1165
+
1166
+ #### Which container of a task answers
1167
+
1168
+ Real ECS sends the request to the container the registration names, on the port it names. Yulin
1169
+ diverges from that on purpose, and it is worth knowing why.
1170
+
1171
+ A great many deployed services put a proxy container, usually nginx, on the port the service
1172
+ registers, with the application listening behind it. Yulin has nothing to run in place of that
1173
+ proxy. There is no image to run and nothing a test could bind to it. Routing strictly by name and
1174
+ port would therefore send every request to a container that is absent here, and the service would
1175
+ answer no request at all, however carefully it was set up.
1176
+
1177
+ So the request goes to a container that is bound, chosen in this order:
1178
+
1179
+ - the container the registration names, when that container is bound
1180
+ - otherwise the bound container that declared the registration's `containerPort`, which settles a
1181
+ choice between two containers that both answer
1182
+ - otherwise the first bound container of the task
1183
+
1184
+ ```typescript
1185
+ // A registration naming the proxy still reaches the application behind it.
1186
+ loadBalancers: [{ targetGroupArn, containerName: "nginx", containerPort: 80 }];
1187
+ ```
1188
+
1189
+ A target group whose service has no bound container at all is answered with a 503 by the load
1190
+ balancer. That is the honest answer, since the tasks are registered and there is nothing behind
1191
+ them.
1192
+
1193
+ `RunTask` cannot run a serving container, for the same reason it cannot run a consuming one. It has
1194
+ no handler that ends and no request to send it. A task started from the same definition records the
1195
+ container as not simulated, with a reason saying to create a service instead.
1196
+
1197
+ ### Updating and deleting a service
1198
+
1199
+ `UpdateService` changes the desired count, the task definition, or both. A new count starts or stops
1200
+ tasks to reach it. A new revision moves the service onto it and replaces every task the service is
1201
+ running. Real ECS replaces them a few at a time under a deployment configuration, and Yulin replaces
1202
+ them at once because nothing here takes any time to start.
1203
+
1204
+ ```typescript sim-ecs-update-service
1205
+ /**
1206
+ * Scaling a simulated ECS service and moving it to a new revision.
1207
+ */
1208
+
1209
+ import {
1210
+ CreateClusterCommand,
1211
+ CreateServiceCommand,
1212
+ ListTasksCommand,
1213
+ RegisterTaskDefinitionCommand,
1214
+ UpdateServiceCommand,
1215
+ } from "@aws-sdk/client-ecs";
1216
+
1217
+ import { SimAws } from "@kensio/yulin";
1218
+
1219
+ const simAws = new SimAws();
1220
+ const ecs = simAws.ecs();
1221
+
1222
+ await ecs.createCluster(new CreateClusterCommand({ clusterName: "orders" }));
1223
+ await ecs.registerTaskDefinition(
1224
+ new RegisterTaskDefinitionCommand({
1225
+ family: "checkout",
1226
+ containerDefinitions: [{ name: "app", image: "checkout:1" }],
1227
+ }),
1228
+ );
1229
+ await ecs.createService(
1230
+ new CreateServiceCommand({
1231
+ cluster: "orders",
1232
+ serviceName: "checkout",
1233
+ taskDefinition: "checkout",
1234
+ desiredCount: 1,
1235
+ }),
1236
+ );
1237
+
1238
+ const scaled = await ecs.updateService(
1239
+ new UpdateServiceCommand({
1240
+ cluster: "orders",
1241
+ service: "checkout",
1242
+ desiredCount: 4,
1243
+ }),
1244
+ );
1245
+
1246
+ console.log(scaled.service?.desiredCount); // 4
1247
+
1248
+ const second = await ecs.registerTaskDefinition(
1249
+ new RegisterTaskDefinitionCommand({
1250
+ family: "checkout",
1251
+ containerDefinitions: [{ name: "app", image: "checkout:2" }],
1252
+ }),
1253
+ );
1254
+
1255
+ const deployed = await ecs.updateService(
1256
+ new UpdateServiceCommand({
1257
+ cluster: "orders",
1258
+ service: "checkout",
1259
+ taskDefinition: "checkout:2",
1260
+ }),
1261
+ );
1262
+
1263
+ console.log(
1264
+ deployed.service?.taskDefinition === second.taskDefinition?.taskDefinitionArn,
1265
+ ); // true
1266
+
1267
+ await simAws.backgroundTasksComplete();
1268
+
1269
+ const listed = await ecs.listTasks(
1270
+ new ListTasksCommand({ cluster: "orders", serviceName: "checkout" }),
1271
+ );
1272
+
1273
+ console.log(listed.taskArns?.length); // 4, all of them on the new revision
1274
+ ```
1275
+
1276
+ `DeleteService` stops the service and its tasks. A service still scaled above zero is refused unless
1277
+ the request forces it, as real ECS refuses one. Scaling to zero first is the ordinary way round. A
1278
+ deleted service is still describable as `INACTIVE`, and its name is free to create again.
1279
+
1280
+ ```typescript sim-ecs-delete-service
1281
+ /**
1282
+ * Deleting a simulated ECS service and the tasks it was keeping running.
1283
+ */
1284
+
1285
+ import {
1286
+ CreateClusterCommand,
1287
+ CreateServiceCommand,
1288
+ DeleteServiceCommand,
1289
+ DescribeServicesCommand,
1290
+ ListTasksCommand,
1291
+ RegisterTaskDefinitionCommand,
1292
+ } from "@aws-sdk/client-ecs";
1293
+
1294
+ import { SimAws } from "@kensio/yulin";
1295
+
1296
+ const simAws = new SimAws();
1297
+ const ecs = simAws.ecs();
1298
+
1299
+ await ecs.createCluster(new CreateClusterCommand({ clusterName: "orders" }));
1300
+ await ecs.registerTaskDefinition(
1301
+ new RegisterTaskDefinitionCommand({
1302
+ family: "checkout",
1303
+ containerDefinitions: [{ name: "app", image: "checkout:1" }],
1304
+ }),
1305
+ );
1306
+ await ecs.createService(
1307
+ new CreateServiceCommand({
1308
+ cluster: "orders",
1309
+ serviceName: "checkout",
1310
+ taskDefinition: "checkout",
1311
+ desiredCount: 2,
1312
+ }),
1313
+ );
1314
+ await simAws.backgroundTasksComplete();
1315
+
1316
+ const deleted = await ecs.deleteService(
1317
+ new DeleteServiceCommand({
1318
+ cluster: "orders",
1319
+ service: "checkout",
1320
+ force: true,
1321
+ }),
1322
+ );
1323
+
1324
+ console.log(deleted.service?.status); // "INACTIVE"
1325
+
1326
+ const listed = await ecs.listTasks(
1327
+ new ListTasksCommand({ cluster: "orders", serviceName: "checkout" }),
1328
+ );
1329
+
1330
+ console.log(listed.taskArns?.length); // 0
1331
+
1332
+ const described = await ecs.describeServices(
1333
+ new DescribeServicesCommand({ cluster: "orders", services: ["checkout"] }),
1334
+ );
1335
+
1336
+ console.log(described.services?.[0]?.runningCount); // 0
1337
+ ```
1338
+
1339
+ Closing the simulated environment with `simAws.close()` stops the tasks of every service in it. A
1340
+ service is kept as state, and nothing is left scheduled either way. A test that finishes with a
1341
+ service running leaves nothing behind it.
1342
+
1343
+ ## Deploying ECS from CloudFormation
1344
+
1345
+ `AWS::ECS::Cluster` creates a simulated cluster, `AWS::ECS::TaskDefinition` registers a simulated
1346
+ task definition revision, and `AWS::ECS::Service` creates a simulated service running it. A test can
1347
+ start from the stack the application is actually defined in, without `RegisterTaskDefinition` and
1348
+ `CreateService` calls written for the test.
1349
+
1350
+ `Ref` on a cluster returns the cluster name and `Fn::GetAtt` `Arn` returns its ARN. `Ref` on a task
1351
+ definition returns the task definition ARN, revision and all, and so does `Fn::GetAtt`
1352
+ `TaskDefinitionArn`. Each deployment registers a new revision, as real CloudFormation does, because
1353
+ a revision is immutable and a changed one is a new revision of the same family.
1354
+
1355
+ Containers are stored as declared, whatever their image, and what makes one of them run is an
1356
+ executable binding supplied at deploy time, in the same `bindings` list a Lambda function handler is
1357
+ supplied in. A container binding targets a container by family and container name, by the logical ID
1358
+ of the task definition that declares it, or by the repository its image comes from.
1359
+
1360
+ ```typescript sim-ecs-cloudformation-task-definition
1361
+ /**
1362
+ * Deploying an ECS stack and binding a handler to one of its containers.
1363
+ */
1364
+
1365
+ import { RunTaskCommand } from "@aws-sdk/client-ecs";
1366
+
1367
+ import { SimAws } from "@kensio/yulin";
1368
+
1369
+ const simAws = new SimAws();
1370
+
1371
+ const processedOrders: string[] = [];
1372
+
1373
+ const stack = await simAws.cloudFormation().deployTemplate({
1374
+ stackName: "orders",
1375
+ template: {
1376
+ Resources: {
1377
+ OrdersCluster: {
1378
+ Type: "AWS::ECS::Cluster",
1379
+ Properties: { ClusterName: "orders" },
1380
+ },
1381
+ WorkerTaskDefinition: {
1382
+ Type: "AWS::ECS::TaskDefinition",
1383
+ Properties: {
1384
+ Family: "orders-worker",
1385
+ Cpu: "512",
1386
+ Memory: "1024",
1387
+ NetworkMode: "awsvpc",
1388
+ RequiresCompatibilities: ["FARGATE"],
1389
+ ContainerDefinitions: [
1390
+ {
1391
+ Name: "app",
1392
+ Image: "example.dkr.ecr.eu-west-2.amazonaws.com/orders-worker:1",
1393
+ Essential: true,
1394
+ Environment: [{ Name: "LOG_LEVEL", Value: "debug" }],
1395
+ },
1396
+ ],
1397
+ },
1398
+ },
1399
+ },
1400
+ Outputs: {
1401
+ TaskDefinition: { Value: { Ref: "WorkerTaskDefinition" } },
1402
+ },
1403
+ },
1404
+ bindings: [
1405
+ {
1406
+ family: "orders-worker",
1407
+ containerName: "app",
1408
+ run: async (): Promise<void> => {
1409
+ await Promise.resolve();
1410
+ processedOrders.push("outstanding orders");
1411
+ },
1412
+ },
1413
+ ],
1414
+ });
1415
+
1416
+ await stack.waitForDeployComplete();
1417
+
1418
+ console.log(stack.output("TaskDefinition"));
1419
+ // "arn:aws:ecs:us-east-1:888888888888:task-definition/orders-worker:1"
1420
+
1421
+ // Running a task from the deployed task definition runs the bound handler.
1422
+ await simAws
1423
+ .ecs()
1424
+ .runTask(
1425
+ new RunTaskCommand({ cluster: "orders", taskDefinition: "orders-worker" }),
1426
+ );
1427
+
1428
+ await simAws.backgroundTasksComplete();
1429
+
1430
+ console.log(processedOrders); // ["outstanding orders"]
1431
+ ```
1432
+
1433
+ A binding can name the task definition Resource instead, which is what a CDK stack gives a test to
1434
+ name. The construct ID is accepted as well as the synthesized logical ID, and the container name can
1435
+ be left out where the task definition declares one container. This is the form to use for a task
1436
+ definition the template declares no `Family` for, since the family CloudFormation generates for it
1437
+ ends in a tail a test would have to deploy the stack to learn. A binding naming an image repository
1438
+ matches any container running an image from it, whichever family declares it, which covers a tag
1439
+ that changes with every build.
1440
+
1441
+ ```typescript sim-ecs-cloudformation-binding-targets
1442
+ /**
1443
+ * Binding a container by the task definition Resource and by its repository.
1444
+ */
1445
+
1446
+ import { SimAws } from "@kensio/yulin";
1447
+
1448
+ const simAws = new SimAws();
1449
+
1450
+ const stack = await simAws.cloudFormation().deployTemplate({
1451
+ stackName: "orders",
1452
+ template: {
1453
+ Resources: {
1454
+ WorkerTaskDefinition: {
1455
+ Type: "AWS::ECS::TaskDefinition",
1456
+ Metadata: { "aws:cdk:path": "OrdersStack/WorkerTask/Resource" },
1457
+ Properties: {
1458
+ Family: "orders-worker",
1459
+ ContainerDefinitions: [
1460
+ {
1461
+ Name: "app",
1462
+ Image: "example.dkr.ecr.eu-west-2.amazonaws.com/orders-worker:1",
1463
+ },
1464
+ {
1465
+ Name: "log-router",
1466
+ Image: "public.ecr.aws/aws-observability/aws-for-fluent-bit:1",
1467
+ },
1468
+ ],
1469
+ },
1470
+ },
1471
+ CheckoutTaskDefinition: {
1472
+ Type: "AWS::ECS::TaskDefinition",
1473
+ Properties: {
1474
+ Family: "orders-checkout",
1475
+ ContainerDefinitions: [
1476
+ {
1477
+ Name: "app",
1478
+ Image: "example.dkr.ecr.eu-west-2.amazonaws.com/checkout:9f21c0",
1479
+ },
1480
+ ],
1481
+ },
1482
+ },
1483
+ },
1484
+ },
1485
+ bindings: [
1486
+ // The CDK construct ID, naming the container because this task definition
1487
+ // declares more than one.
1488
+ {
1489
+ logicalId: "WorkerTask",
1490
+ containerName: "app",
1491
+ run: (): void => {
1492
+ // Whatever the worker does.
1493
+ },
1494
+ },
1495
+ // Any container running an image from this repository, whatever its tag.
1496
+ {
1497
+ imageRepository: "example.dkr.ecr.eu-west-2.amazonaws.com/checkout",
1498
+ run: (): void => {
1499
+ // Whatever the checkout container does.
1500
+ },
1501
+ },
1502
+ ],
1503
+ });
1504
+
1505
+ await stack.waitForDeployComplete();
1506
+
1507
+ console.log(simAws.ecs().taskDefinition("orders-worker").revision); // 1
1508
+ ```
1509
+
1510
+ A binding that resolves to no Resource in the stack fails the deployment naming the binding, since
1511
+ the usual cause is a container renamed in the template and not in the test. A container with no
1512
+ binding is a different thing. The stack deploys, the container is stored as declared, and it is
1513
+ recorded as not simulated when a task runs. That is what lets a task definition holding a log router
1514
+ and an observability agent alongside the application deploy and run.
1515
+
1516
+ A task definition's `TaskRoleArn` and `ExecutionRoleArn` resolve whether the template gives an ARN
1517
+ or a `Ref` to an `AWS::IAM::Role` of the same stack, and a container's AWS calls are authorized as
1518
+ the role the stack deploys.
1519
+
1520
+ ### Deploying a service
1521
+
1522
+ `AWS::ECS::Service` creates a service in its cluster, keeping `DesiredCount` tasks of the task
1523
+ definition it names running. `Cluster` takes a `Ref` to a cluster of the same stack or a cluster
1524
+ ARN, and `TaskDefinition` takes a `Ref` to a task definition of the same stack, which pins the
1525
+ revision the deployment registered, or an ARN, or a family. A container bound at deploy time is
1526
+ running once the stack has deployed, because the service names the task definition and is created
1527
+ after it.
1528
+
1529
+ `Ref` on a service returns the service ARN, and so does `Fn::GetAtt` `ServiceArn`. `Fn::GetAtt`
1530
+ `Name` returns the service name. `simAws.ecs().service(name, cluster)` reads the simulated service
1531
+ itself, by name in a cluster or by its full ARN.
1532
+
1533
+ ```typescript sim-ecs-cloudformation-service
1534
+ /**
1535
+ * Deploying an ECS service and reading what it is keeping running.
1536
+ */
1537
+
1538
+ import { ListTasksCommand } from "@aws-sdk/client-ecs";
1539
+
1540
+ import { SimAws } from "@kensio/yulin";
1541
+
1542
+ const simAws = new SimAws();
1543
+
1544
+ const stack = await simAws.cloudFormation().deployTemplate({
1545
+ stackName: "orders",
1546
+ template: {
1547
+ Resources: {
1548
+ OrdersCluster: {
1549
+ Type: "AWS::ECS::Cluster",
1550
+ Properties: { ClusterName: "orders" },
1551
+ },
1552
+ WorkerTaskDefinition: {
1553
+ Type: "AWS::ECS::TaskDefinition",
1554
+ Properties: {
1555
+ Family: "orders-worker",
1556
+ ContainerDefinitions: [
1557
+ {
1558
+ Name: "app",
1559
+ Image: "example.dkr.ecr.eu-west-2.amazonaws.com/orders-worker:1",
1560
+ },
1561
+ ],
1562
+ },
1563
+ },
1564
+ WorkerService: {
1565
+ Type: "AWS::ECS::Service",
1566
+ Properties: {
1567
+ ServiceName: "orders-worker",
1568
+ Cluster: { Ref: "OrdersCluster" },
1569
+ TaskDefinition: { Ref: "WorkerTaskDefinition" },
1570
+ DesiredCount: 2,
1571
+ LaunchType: "FARGATE",
1572
+ },
1573
+ },
1574
+ },
1575
+ Outputs: {
1576
+ Service: { Value: { Ref: "WorkerService" } },
1577
+ ServiceName: { Value: { "Fn::GetAtt": ["WorkerService", "Name"] } },
1578
+ },
1579
+ },
1580
+ bindings: [
1581
+ {
1582
+ logicalId: "WorkerTaskDefinition",
1583
+ run: (): void => {
1584
+ // Whatever the worker container does when something reaches it.
1585
+ },
1586
+ },
1587
+ ],
1588
+ });
1589
+
1590
+ await stack.waitForDeployComplete();
1591
+ await simAws.backgroundTasksComplete();
1592
+
1593
+ console.log(stack.output("Service"));
1594
+ // "arn:aws:ecs:us-east-1:888888888888:service/orders/orders-worker"
1595
+ console.log(stack.output("ServiceName")); // "orders-worker"
1596
+
1597
+ const service = simAws.ecs().service("orders-worker", "orders");
1598
+
1599
+ console.log(service.desiredCount); // 2
1600
+
1601
+ const listed = await simAws
1602
+ .ecs()
1603
+ .listTasks(
1604
+ new ListTasksCommand({ cluster: "orders", serviceName: "orders-worker" }),
1605
+ );
1606
+
1607
+ console.log(listed.taskArns?.length); // 2
1608
+ ```
1609
+
1610
+ An unnamed service is named after the stack, the logical ID and a tail derived from both, as a
1611
+ cluster and a family are. `Fn::GetAtt ... Name` answers with the generated name. A service declaring
1612
+ no `DesiredCount` keeps one task running, which is what real CloudFormation gives a new service.
1613
+
1614
+ Updating the stack moves the service. A changed `DesiredCount` scales it, and a changed task
1615
+ definition moves it onto the revision the update registered, replacing the tasks it was keeping.
1616
+ Tearing the stack down deletes the service, stopping its tasks and leaving it `INACTIVE`, whatever
1617
+ it was scaled to.
1618
+
1619
+ `LoadBalancers` registers the service's tasks into the target group it names. A template that
1620
+ declares a load balancer, a target group and a service therefore deploys something that answers. The
1621
+ target group has to exist and be an `ip` one, or the deployment fails naming it, and what the
1622
+ template declared is readable on the simulated service:
1623
+
1624
+ ```typescript
1625
+ console.log(simAws.ecs().service("orders-worker", "orders").loadBalancers);
1626
+ ```
1627
+
1628
+ Everything else the three Resource types declare is stored as declared, or recorded as ignored where
1629
+ this simulation has no use for it. `CapacityProviders`, `DefaultCapacityProviderStrategy` and
1630
+ `ServiceConnectDefaults` on a cluster, `InferenceAccelerators` and `EnableFaultInjection` on a task
1631
+ definition, and a service's `NetworkConfiguration`, `CapacityProviderStrategy`,
1632
+ `DeploymentConfiguration` and `ServiceRegistries` among the rest, are read and ignored while the
1633
+ stack deploys, and each one is reported on the Resource:
1634
+
1635
+ ```typescript
1636
+ console.log(stack.getResource("OrdersCluster")?.ignoredProperties);
1637
+ ```
1638
+
1639
+ ## Authorization
1640
+
1641
+ Every operation is authorized by simulated IAM against the real ECS action.
1642
+
1643
+ Real ECS gives the task definition operations no resource type at all, and gives `CreateCluster`,
1644
+ `ListClusters` and `ListTasks` none either, and all of those authorize against `*`. A policy naming
1645
+ a task definition ARN grants none of them, here as on AWS. The operations that do take a resource
1646
+ are `DescribeClusters` and `DeleteCluster`, which take the cluster's ARN, `RunTask`, which takes the
1647
+ task definition revision it would run, `DescribeTasks` and `StopTask`, which take the task's ARN,
1648
+ and the service operations, which take the service's ARN. `CreateService` authorizes against the ARN
1649
+ the service is about to have, and a policy can therefore name one service by name before it exists.
1650
+
1651
+ A registration carrying a `taskRoleArn` or an `executionRoleArn` hands ECS a Role it runs the task
1652
+ as, and `RegisterTaskDefinition` authorizes `iam:PassRole` against each of them. Either Role left out
1653
+ passes nothing. See [passing a Role to a service](https://yulinsim.dev/services/iam/#passing-a-role-to-a-service) in the IAM docs.
1654
+
1655
+ ```typescript sim-ecs-iam-policy
1656
+ /**
1657
+ * A simulated IAM policy allowing a Role to register task definitions.
1658
+ */
1659
+
1660
+ import { RegisterTaskDefinitionCommand } from "@aws-sdk/client-ecs";
1661
+ import { CreateRoleCommand, PutRolePolicyCommand } from "@aws-sdk/client-iam";
1662
+
1663
+ import { SimAws } from "@kensio/yulin";
1664
+
1665
+ const simAws = new SimAws();
1666
+ const accountId = simAws.defaultAccountId;
1667
+
1668
+ const role = await simAws.iam().createRole(
1669
+ new CreateRoleCommand({
1670
+ RoleName: "Deployer",
1671
+ AssumeRolePolicyDocument: JSON.stringify({
1672
+ Version: "2012-10-17",
1673
+ Statement: {
1674
+ Effect: "Allow",
1675
+ Principal: { AWS: `arn:aws:iam::${accountId}:root` },
1676
+ Action: "sts:AssumeRole",
1677
+ },
1678
+ }),
1679
+ }),
1680
+ );
1681
+
1682
+ await simAws.iam().putRolePolicy(
1683
+ new PutRolePolicyCommand({
1684
+ RoleName: "Deployer",
1685
+ PolicyName: "RegisterTaskDefinitions",
1686
+ PolicyDocument: JSON.stringify({
1687
+ Version: "2012-10-17",
1688
+ Statement: {
1689
+ Effect: "Allow",
1690
+ Action: "ecs:RegisterTaskDefinition",
1691
+ // ECS gives this action no resource type, so only `*` grants it.
1692
+ Resource: "*",
1693
+ },
1694
+ }),
1695
+ }),
1696
+ );
1697
+
1698
+ const registered = await simAws.ecs().registerTaskDefinition(
1699
+ new RegisterTaskDefinitionCommand({
1700
+ family: "checkout",
1701
+ containerDefinitions: [{ name: "app", image: "checkout:1" }],
1702
+ }),
1703
+ { caller: { kind: "arn", arn: role.Role.Arn } },
1704
+ );
1705
+
1706
+ console.log(registered.taskDefinition?.registeredBy); // the Role ARN
1707
+ ```
1708
+
1709
+ A registered revision records the caller that registered it as `registeredBy`, as real ECS does.
1710
+
1711
+ ## Scoping by account and region
1712
+
1713
+ Clusters and task definitions belong to one account and region. A family registered in one region
1714
+ has its own revision numbering and its own ARNs.
1715
+
1716
+ ```typescript sim-ecs-scoping
1717
+ /**
1718
+ * Simulated ECS state in two account and region scopes.
1719
+ */
1720
+
1721
+ import {
1722
+ ListTaskDefinitionsCommand,
1723
+ RegisterTaskDefinitionCommand,
1724
+ } from "@aws-sdk/client-ecs";
1725
+
1726
+ import { SimAws } from "@kensio/yulin";
1727
+
1728
+ const simAws = new SimAws();
1729
+
1730
+ const registered = await simAws
1731
+ .account("222222222222")
1732
+ .region("us-east-1")
1733
+ .ecs()
1734
+ .registerTaskDefinition(
1735
+ new RegisterTaskDefinitionCommand({
1736
+ family: "checkout",
1737
+ containerDefinitions: [{ name: "app", image: "checkout:1" }],
1738
+ }),
1739
+ );
1740
+
1741
+ console.log(registered.taskDefinition?.taskDefinitionArn);
1742
+ // "arn:aws:ecs:us-east-1:222222222222:task-definition/checkout:1"
1743
+
1744
+ const elsewhere = await simAws
1745
+ .account("222222222222")
1746
+ .region("eu-west-2")
1747
+ .ecs()
1748
+ .listTaskDefinitions(new ListTaskDefinitionsCommand({}));
1749
+
1750
+ console.log(elsewhere.taskDefinitionArns?.length); // 0
1751
+ ```
1752
+
1753
+ ## Using it through an ECS SDK client
1754
+
1755
+ Ordinary SDK code reaches simulated ECS through `SimSdk`, without a Yulin type appearing in the code
1756
+ under test.
1757
+
1758
+ ```typescript sim-ecs-sdk-interception
1759
+ /**
1760
+ * Reaching simulated ECS through an intercepted ECS SDK client.
1761
+ */
1762
+
1763
+ import {
1764
+ DescribeTaskDefinitionCommand,
1765
+ ECSClient,
1766
+ RegisterTaskDefinitionCommand,
1767
+ } from "@aws-sdk/client-ecs";
1768
+
1769
+ import { SimSdk } from "@kensio/yulin/sdk";
1770
+
1771
+ using simSdk = new SimSdk();
1772
+ simSdk.intercept(ECSClient);
1773
+
1774
+ const ecs = new ECSClient({ region: "eu-west-2" });
1775
+
1776
+ await ecs.send(
1777
+ new RegisterTaskDefinitionCommand({
1778
+ family: "checkout",
1779
+ containerDefinitions: [{ name: "app", image: "checkout:1" }],
1780
+ }),
1781
+ );
1782
+
1783
+ const described = await ecs.send(
1784
+ new DescribeTaskDefinitionCommand({ taskDefinition: "checkout" }),
1785
+ );
1786
+
1787
+ console.log(described.taskDefinition?.revision); // 1
1788
+ ```
1789
+
1790
+ ## Available functionality
1791
+
1792
+ - `RegisterTaskDefinitionCommand`, with revisions numbered per family from one, authorizing
1793
+ `iam:PassRole` on the `taskRoleArn` and the `executionRoleArn` it names
1794
+ - `DeregisterTaskDefinitionCommand`, marking one revision `INACTIVE`
1795
+ - `DescribeTaskDefinitionCommand`, taking a family, a `family:revision` or a full ARN
1796
+ - `ListTaskDefinitionsCommand` and `ListTaskDefinitionFamiliesCommand`, with prefix, status and
1797
+ paging
1798
+ - `CreateClusterCommand`, `DescribeClustersCommand`, `ListClustersCommand` and
1799
+ `DeleteClusterCommand`
1800
+ - `RunTaskCommand`, running the handlers bound to a task definition's containers, up to a `count` of
1801
+ ten tasks at a time
1802
+ - `DescribeTasksCommand`, `ListTasksCommand` and `StopTaskCommand`
1803
+ - Tasks run from an EventBridge rule target or a Scheduler schedule target, through that target's
1804
+ role
1805
+ - `CreateServiceCommand`, keeping a desired count of tasks running from a task definition
1806
+ - `UpdateServiceCommand`, changing the desired count, the task definition, or both
1807
+ - `DescribeServicesCommand` and `DeleteServiceCommand`, with the `force` a scaled-up service needs
1808
+ - `ListTasks` filtering by `serviceName`, listing a service's tasks on their own
1809
+ - `bindContainer`, targeting a container by family and container name or by image repository
1810
+ - A container binding that `consumes` a simulated SQS queue, with Yulin driving the polling loop on
1811
+ the simulated clock while the service is running
1812
+ - `AWS::ECS::Cluster`, answering `Ref` with the cluster name and `Fn::GetAtt` with `Arn`
1813
+ - `AWS::ECS::TaskDefinition`, registering a revision and answering `Ref` with its ARN
1814
+ - `AWS::ECS::Service`, running the task definition it names at the desired count it declares, and
1815
+ answering `Ref` with the service ARN and `Fn::GetAtt` with `Name` and `ServiceArn`
1816
+ - A stack update that scales a service or moves it onto a new revision, and a teardown that deletes
1817
+ it
1818
+ - A container binding that answers `http` requests, called once per request a load balancer routes
1819
+ to the container
1820
+ - `loadBalancers` on a service, registering each of its tasks into the target group it names and
1821
+ deregistering them as they stop
1822
+ - `LoadBalancers` on an `AWS::ECS::Service`, deploying into the same registration
1823
+ - `simAws.ecs().service()`, reading a simulated service by name in a cluster or by its ARN
1824
+ - Deploy-time container bindings, targeting a container by family and container name, by the task
1825
+ definition's logical ID or CDK construct ID, or by its image repository, and declaring `run` or
1826
+ `consumes` as a directly bound container does
1827
+ - Task and execution roles resolved from a `Ref` to a same-stack role or from an ARN
1828
+ - Container environment variables and `RunTask` container overrides, through `process.env`
1829
+ - Container AWS calls authorized as the task role, including a `RunTask` `taskRoleArn` override
1830
+ - Container `secrets` resolved from simulated Secrets Manager and SSM Parameter Store as the
1831
+ execution role, including a JSON key selector and a `SecureString` parameter
1832
+ - Container definitions stored and reported as declared, whatever their image
1833
+ - Task definition tags, reported under `include: ["TAGS"]`
1834
+ - Cluster settings, configuration and tags, reported under their matching `include` values
1835
+ - Authorization of every operation by simulated IAM, against the real IAM action and resource
1836
+ - Clusters, task definitions, tasks and services scoped by account and region
1837
+ - ECS SDK clients intercepted by `SimSdk`
1838
+
1839
+ ## Limitations
1840
+
1841
+ Current documented limitations:
1842
+
1843
+ - Image contents are never read. An image URI is an identifier and nothing more. No image is pulled,
1844
+ inspected or run, and a tag no registry holds is stored without complaint.
1845
+ - A container definition field the `SimEcsContainerDefinitionType` shape leaves unnamed, such as
1846
+ `hostname` or `links`, is still stored and reported back. The shape names the fields the docs
1847
+ describe rather than every field ECS has, and it deliberately carries no index signature, since
1848
+ one would stop a real SDK command input being passed straight in.
1849
+ - `ListTaskDefinitions` refuses a `status` of `DELETE_IN_PROGRESS`, which real ECS accepts. Nothing
1850
+ deletes a task definition here, and the answer would always be an empty listing. Refusing says so,
1851
+ where a result would look like an answer.
1852
+ - Yulin creates no cluster on its own, including the `default` one. A `RunTask` request naming no
1853
+ cluster needs one to have been created already. An AWS account often has a `default` cluster,
1854
+ created the first time ECS was used from the console.
1855
+ - A container of a task definition with no `taskRoleArn` is denied every AWS call. It never falls
1856
+ back to whatever credentials a container instance role might have supplied.
1857
+ - Only a bound container runs. A container with no binding never starts and is reported with a
1858
+ reason saying so, and a task with no binding at all stops with `TaskFailedToStart`.
1859
+ - Containers run one after another in the order the task definition declares them, and never
1860
+ alongside each other. `dependsOn`, `essential`, health checks and `startTimeout` are stored and
1861
+ ignored.
1862
+ - `RunTask` refuses `networkConfiguration`, `capacityProviderStrategy`, `platformVersion`, `tags`,
1863
+ `placementConstraints`, `placementStrategy` and the rest of what it takes. There is no network and
1864
+ no capacity here for any of them to apply to.
1865
+ - A `RunTask` override may name `taskRoleArn` and a container's `environment`. A `command`, `cpu` or
1866
+ `memory` override is refused, since Yulin never runs an image and has no capacity to allocate.
1867
+ - The execution role resolves container `secrets` and does nothing else. There is no image to pull
1868
+ and no log driver to write to.
1869
+ - A container secret can only come from simulated Secrets Manager or simulated SSM Parameter Store.
1870
+ A `valueFrom` naming anything else stops the task outright. Secret rotation is absent, and a task
1871
+ always reads the version that is current when it starts.
1872
+ - A JSON key selector resolves only where the key holds a string. A key holding a number, a boolean
1873
+ or a nested object is refused, because an environment variable is text and real ECS does not
1874
+ document which text it would become.
1875
+ - A secret holding a binary value is refused. Real ECS cannot put one in an environment variable
1876
+ either, but it reports the problem differently.
1877
+ - `secretOptions` on a `logConfiguration` are stored and never resolved. There is no log driver here
1878
+ for them to configure.
1879
+ - A task definition declaring `secrets` and no `executionRoleArn` fails when a task is run, and not
1880
+ when the revision is registered. Real ECS refuses the registration.
1881
+ - An EventBridge or Scheduler target's `EcsParameters` takes `TaskDefinitionArn` and `TaskCount`
1882
+ only, taking and ignoring the launch type, platform version, network configuration and capacity
1883
+ provider strategy for the same reason `RunTask` refuses them, which is that there is no placement
1884
+ and no network here. A `TaskCount` above one runs that many simulated tasks, and a bound container
1885
+ handler runs once for each of them, in this process and one after another.
1886
+ - A rule or schedule target's `Input` is read as the task's overrides, since a task has nowhere to
1887
+ receive a payload. Container environment variables are set with a `containerOverrides` list naming
1888
+ the container.
1889
+ - A service's desired count is simulated as state, and not as concurrency. Three tasks exist and are
1890
+ reported as running, and the handler bound to a container is called once per request or per poll.
1891
+ Yulin runs in one Node.js process, with nothing to copy.
1892
+ - A bound container of a service is treated as running and stays available until the service is
1893
+ scaled to zero, deleted, or its `SimAws` is closed. What calls its handler is whatever reaches it,
1894
+ either a queue it consumes or a request a load balancer routes to it.
1895
+ - A consuming container's loop belongs to Yulin, and not to the container. A binding supplies what
1896
+ happens to a batch, and a polling loop written inside your own container code is never run,
1897
+ because an endless loop in a single Node.js process never yields to the test running it.
1898
+ - A container can only consume a simulated SQS queue, in the same account and region as the service.
1899
+ Nothing else is a source, and a queue URL naming another scope reaches no queue.
1900
+ - Yulin polls once per service and container, and never once per task, and a desired count above one
1901
+ hands a batch over exactly once. It also polls in response to something, and never continuously.
1902
+ The triggers are a message arriving, a batch coming back, or a full batch suggesting there is more
1903
+ waiting.
1904
+ - Long polling is absent. A `WaitTimeSeconds` is meaningless when the queue itself says when there
1905
+ is something to poll for, and the setting is gone.
1906
+ - A consuming container handles a batch all or nothing. There is no partial batch response, since
1907
+ that is a Lambda event source feature and not something a worker container has.
1908
+ - A consuming or serving container of a task started by `RunTask` records the same not-simulated
1909
+ reason an unbound container does. Neither has a handler that ends, and a run task has to end.
1910
+ - A service's tasks come up all at once and are replaced all at once. There are no deployments,
1911
+ deployment controllers, circuit breakers or rolling replacement, so `DescribeServices` reports no
1912
+ `deployments` and no `events`, and `UpdateService` refuses `forceNewDeployment`.
1913
+ - A service whose tasks fail to start never starts replacements for them. Real ECS keeps trying,
1914
+ which in a test would be an endless retry with nothing to assert on. The service here reports the
1915
+ running count it actually has.
1916
+ - A `loadBalancers` entry needs a `targetGroupArn`, a `containerName` and a `containerPort` between
1917
+ 1 and 65535, and the target group has to be an `ip` one in the service's own account and region. A
1918
+ `loadBalancerName` is the Classic Load Balancer form, and is refused, as is a target group that
1919
+ was never created.
1920
+ - Which container of a task a request reaches diverges from real ECS on purpose. Real ECS routes to
1921
+ the container the registration names, on the port it names. Here the request goes to a bound
1922
+ container, which is the one the registration names where that one is bound, otherwise the bound
1923
+ container declaring the registration's port, otherwise the first bound one. The common real task
1924
+ puts an unsimulated proxy on the registered port, and routing strictly would reach a container
1925
+ absent here.
1926
+ - A service registered into a target group with no bound container is answered with a 503 by the
1927
+ load balancer. The tasks are registered and there is nothing behind them.
1928
+ - A task is registered as a target as soon as the service starts it and deregistered as soon as it
1929
+ stops. There are no health checks, no target health states, no deregistration delay and no
1930
+ connection draining. The address a task is registered under is counted, since there is no network
1931
+ interface to take one from.
1932
+ - Requests are never shared between a service's tasks. The desired count is state and not
1933
+ concurrency, and a target group holding three targets calls one handler.
1934
+ - `CreateService` refuses `serviceRegistries`, `networkConfiguration`, `deploymentConfiguration`,
1935
+ `capacityProviderStrategy` and the rest of what it takes. There is no network or capacity here for
1936
+ any of them to apply to.
1937
+ - `UpdateService` changes the desired count and the task definition alone. A service's load balancer
1938
+ registration stays as it was created, and its tasks are registered and deregistered as the count
1939
+ changes.
1940
+ - `CreateService` refuses a `schedulingStrategy` of `DAEMON`, which places one task on each
1941
+ container instance. There are no container instances here to place one on each of.
1942
+ - `CreateService` needs a `desiredCount`, as a replica service does on real ECS, and it can be zero.
1943
+ - A service's tasks are `startedBy` `ecs-svc/` and the service name. Real ECS uses `ecs-svc/` and a
1944
+ number, which would name nothing here.
1945
+ - Service autoscaling, service discovery and `ListServices` are all absent.
1946
+ - `StartTask` is absent.
1947
+ - Closing a `SimAws` stops the tasks of every service in it. The services stay describable with the
1948
+ desired count they had, and their tasks are not brought back.
1949
+ - `DescribeTasks` refuses `include`, and a task carries no tags, so `RunTask` refuses `tags` too.
1950
+ - `ListTasks` refuses a `desiredStatus` of `PENDING`, which real ECS accepts. A simulated task is
1951
+ wanted either running or stopped, so the answer would always be an empty listing.
1952
+ - A stopped task is kept for as long as the simulation lasts. Real ECS stops reporting one about an
1953
+ hour after it stops.
1954
+ - A task reports no `cpu`, `memory`, `connectivity`, `attachments` or `availabilityZone`. There is
1955
+ no capacity and no network here to report.
1956
+ - A described task definition reports neither `compatibilities` nor `requiresAttributes`. Real ECS
1957
+ works both out from what the definition declares, which would mean reading a container
1958
+ definition's meaning.
1959
+ - `RegisterTaskDefinition` refuses a setting this simulation has no room for, and never drops one
1960
+ silently. `inferenceAccelerators` and `enableFaultInjection` are refused for that reason.
1961
+ - Nothing is defaulted. A revision registered without `networkMode` describes without one, where
1962
+ real ECS would have chosen `bridge`, because that value would be made up here.
1963
+ - `CreateCluster` refuses `capacityProviders`, `defaultCapacityProviderStrategy` and
1964
+ `serviceConnectDefaults`. There is no capacity and no service discovery here to attach them to.
1965
+ - `DescribeClusters` refuses `include` values of `ATTACHMENTS` and `STATISTICS`, and always reports
1966
+ zero for every count, tasks in the cluster included. `ListTasks` is what reports the tasks a
1967
+ cluster holds.
1968
+ - `ListClusters` leaves out a deleted cluster, which is still describable by name or ARN as
1969
+ `INACTIVE`.
1970
+ - `CreateCluster` with a name an active cluster already has hands that cluster back, as real ECS
1971
+ does. The settings, configuration and tags on the second request are ignored.
1972
+ - Deleting a cluster is immediate, and never fails for a cluster still holding running tasks or
1973
+ active services, which real ECS refuses. The services in it go on reporting what they are keeping.
1974
+ - `DescribeClusters` reports zero services and zero tasks whatever the cluster holds.
1975
+ `DescribeServices` and `ListTasks` are what report those.
1976
+ - Task definition and cluster tags are stored and reported, but `TagResource`, `UntagResource` and
1977
+ `ListTagsForResource` are absent.
1978
+ - An unnamed cluster, task definition or service gets a name composed from the stack name, the
1979
+ logical ID and a tail derived from both, in place of the random characters real CloudFormation
1980
+ ends one with. The same stack redeployed gets the same names, and a stack deployed twice under
1981
+ different names gets two different families.
1982
+ [the CloudFormation docs](https://yulinsim.dev/services/cloudformation/#names-cloudformation-generates "Names CloudFormation generates")
1983
+ cover the tail and how a long name is trimmed.
1984
+ - A service that declares no `DesiredCount` keeps one task running. That is the default real
1985
+ CloudFormation documents for a new service. A `DesiredCount` written as the text of a number is
1986
+ taken as that number, since a String Parameter resolves to text, and anything else is refused
1987
+ naming the Resource and the property, a fraction of a task included.
1988
+ - An update replaces a task definition Resource in full, registering a new revision and
1989
+ deregistering the one it replaced. The family accumulates revisions with only the newest one
1990
+ `ACTIVE`, where real CloudFormation leaves the earlier revisions active.
1991
+ - An update replaces a service Resource as well, and a scaled or redeployed service is deleted and
1992
+ created again. Its tasks stop and new ones start, and its ARN is unchanged as long as its name is,
1993
+ since a service ARN is its cluster and its name.
1994
+ - A stack teardown deletes its cluster, leaving it `INACTIVE`, deregisters the revision it
1995
+ registered, leaving that `INACTIVE`, and deletes its service, leaving that `INACTIVE` too. None of
1996
+ them is removed, and revision numbers are not freed. The service is deleted with force, because
1997
+ real CloudFormation scales one to zero on its way out and nothing here needs the time that takes.
1998
+ - CloudFormation property names are translated to the API's by lowering the first letter of each of
1999
+ them, all the way down, apart from `EFSVolumeConfiguration`,
2000
+ `FSxWindowsFileServerVolumeConfiguration` and `ProxyConfigurationProperties`, which the API spells
2001
+ differently. `DockerLabels`, `Options`, `DriverOpts` and `Labels` hold keys the template wrote, so
2002
+ those are left alone. A name outside all of that is stored under the name lowering gives it.
2003
+ - `CapacityProviders`, `DefaultCapacityProviderStrategy` and `ServiceConnectDefaults` on a cluster,
2004
+ `InferenceAccelerators` and `EnableFaultInjection` on a task definition, and everything a service
2005
+ declares beyond its cluster, task definition, count, launch type, scheduling strategy and load
2006
+ balancers, are read and recorded as ignored, where the equivalent SDK request is refused. A stack
2007
+ that fails to deploy is worth less to a test than a Resource missing a property this simulation
2008
+ has no use for.
2009
+ - A deploy-time binding is checked against the template as the stack is built. A family, a container
2010
+ name or an image repository built from another Resource's attribute, in place of a plain string,
2011
+ resolves to no target and fails the deployment.
2012
+ - A `TaskRoleArn` or `ExecutionRoleArn` given as a `Ref` to a role resolves to the role name, and
2013
+ that name is turned into the ARN it would have at the default path. A role declaring a `Path` of
2014
+ its own therefore resolves to an ARN without it. An `AWS::Lambda::Function` `Role` already behaves
2015
+ this way, so the two agree, and naming the role by `Fn::GetAtt` `Arn` gets the real ARN either
2016
+ way.
2017
+ - Cluster and family names are validated to the 255 letters, numbers, hyphens and underscores real
2018
+ ECS accepts, but error messages differ from the real ones.
2019
+ - Account-wide limits are absent. No request fails for having registered too many task definitions
2020
+ or created too many clusters.
2021
+ - `serveSimAws` serves no ECS HTTP API.