@kensio/yulin 1.21.5 → 1.21.7

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 (99) hide show
  1. package/dist/service/cloudformation/resource/cfn/sim-cfn-default-resource-value-adapter.d.ts +8 -1
  2. package/dist/service/cloudformation/resource/cfn/sim-cfn-default-resource-value-adapter.js +9 -2
  3. package/dist/service/cloudformation/resource/cfn/sim-cfn-resource-value-adapter.d.ts +6 -1
  4. package/dist/service/cloudformation/resource/cfn/sim-cfn-resource-value-adapter.js +2 -1
  5. package/dist/service/cloudformation/resource/create/sim-cfn-resource-create-operation.d.ts +4 -0
  6. package/dist/service/cloudformation/resource/create/sim-cfn-resource-create-operation.js +6 -0
  7. package/dist/service/cloudformation/resource/sim-cfn-resource-record.d.ts +19 -0
  8. package/dist/service/cloudformation/resource/sim-cfn-resource-record.js +26 -0
  9. package/dist/service/cloudformation/resource/sim-cfn-resource.js +1 -0
  10. package/dist/service/cloudformation/resource/unsupported/sim-cfn-named-skip.d.ts +26 -0
  11. package/dist/service/cloudformation/resource/unsupported/sim-cfn-named-skip.js +31 -0
  12. package/dist/service/cloudfront/cfn/cache-policy/sim-cfn-cf-cache-policy-creator.d.ts +5 -2
  13. package/dist/service/cloudfront/cfn/cache-policy/sim-cfn-cf-cache-policy-creator.js +10 -2
  14. package/dist/service/cloudfront/cfn/origin-access-control/sim-cfn-cf-oac-creator.d.ts +5 -2
  15. package/dist/service/cloudfront/cfn/origin-access-control/sim-cfn-cf-oac-creator.js +10 -2
  16. package/dist/service/cloudfront/cfn/origin-request-policy/sim-cfn-cf-orp-creator.d.ts +5 -2
  17. package/dist/service/cloudfront/cfn/origin-request-policy/sim-cfn-cf-orp-creator.js +10 -2
  18. package/dist/service/cloudfront/cfn/policy/sim-cfn-cf-policy-creators.d.ts +33 -0
  19. package/dist/service/cloudfront/cfn/policy/sim-cfn-cf-policy-creators.js +39 -0
  20. package/dist/service/cloudfront/cfn/response-headers-policy/sim-cfn-cf-rh-policy-creator.d.ts +5 -2
  21. package/dist/service/cloudfront/cfn/response-headers-policy/sim-cfn-cf-rh-policy-creator.js +10 -2
  22. package/dist/service/cloudfront/cfn/sim-cfn-cf-authorizer.d.ts +40 -0
  23. package/dist/service/cloudfront/cfn/sim-cfn-cf-authorizer.js +50 -0
  24. package/dist/service/cloudfront/cfn/sim-cfn-cloudfront-resource-factory.d.ts +1 -4
  25. package/dist/service/cloudfront/cfn/sim-cfn-cloudfront-resource-factory.js +14 -46
  26. package/dist/service/cloudfront/sim-cloudfront-commands.d.ts +6 -0
  27. package/dist/service/cloudfront/sim-cloudfront-commands.js +7 -0
  28. package/dist/service/cloudfront/sim-cloudfront.d.ts +6 -0
  29. package/dist/service/cloudfront/sim-cloudfront.js +7 -0
  30. package/dist/service/dynamodb/document/sim-dynamodb-document-command-paths.d.ts +8 -0
  31. package/dist/service/dynamodb/document/sim-dynamodb-document-command-paths.js +62 -0
  32. package/dist/service/dynamodb/document/sim-dynamodb-document-routes.d.ts +1 -1
  33. package/dist/service/dynamodb/document/sim-dynamodb-document-routes.js +3 -1
  34. package/dist/service/eventbridge/cfn/bus/sim-cfn-event-bus-creator.js +1 -0
  35. package/dist/service/eventbridge/cfn/bus/sim-cfn-event-bus-properties.d.ts +4 -0
  36. package/dist/service/eventbridge/cfn/bus/sim-cfn-event-bus-properties.js +26 -1
  37. package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-creator.js +3 -1
  38. package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-properties.d.ts +0 -7
  39. package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-properties.js +0 -20
  40. package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-unsimulated-properties.d.ts +13 -0
  41. package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-unsimulated-properties.js +59 -0
  42. package/dist/service/eventbridge/cfn/sim-cfn-event-bridge-resource-error.d.ts +1 -1
  43. package/dist/service/eventbridge/cfn/sim-cfn-event-bridge-resource-error.js +1 -1
  44. package/dist/service/iam/authorize/sim-iam-action-authorizer.d.ts +8 -3
  45. package/dist/service/iam/authorize/sim-iam-action-authorizer.js +13 -4
  46. package/dist/service/iam/cfn/role/sim-cfn-iam-role-creator.js +1 -0
  47. package/dist/service/iam/cfn/role/sim-cfn-iam-role-properties-parser.d.ts +1 -0
  48. package/dist/service/iam/cfn/role/sim-cfn-iam-role-properties-parser.js +21 -14
  49. package/dist/service/iam/cfn/{sim-cfn-iam-optional-string.d.ts → sim-cfn-iam-property.d.ts} +10 -2
  50. package/dist/service/iam/cfn/sim-cfn-iam-property.js +31 -0
  51. package/dist/service/iam/cfn/user/sim-cfn-iam-user-properties-parser.js +1 -1
  52. package/dist/service/iam/command/role/create-role/create-role-record-factory.js +3 -0
  53. package/dist/service/iam/command/role/create-role/create-role.command.d.ts +10 -0
  54. package/dist/service/iam/command/role/get-role/get-role.command.d.ts +2 -0
  55. package/dist/service/iam/command/role/get-role/get-role.handler.js +2 -0
  56. package/dist/service/iam/command/role/sim-iam-role-command-handlers.js +15 -1
  57. package/dist/service/iam/role/sim-iam-role-boundary.d.ts +24 -0
  58. package/dist/service/iam/role/sim-iam-role-boundary.js +24 -0
  59. package/dist/service/iam/role/sim-iam-role-with-policy.factory.d.ts +1 -0
  60. package/dist/service/iam/role/sim-iam-role.d.ts +10 -0
  61. package/dist/service/iam/serve/sim-iam-query-entity.d.ts +6 -1
  62. package/dist/service/iam/serve/sim-iam-query-entity.js +10 -4
  63. package/dist/service/iam/serve/sim-iam-query-role-operations.js +15 -3
  64. package/dist/service/lambda/cfn/event-source-mapping/sim-cfn-lambda-event-source-mapping-properties.js +1 -1
  65. package/dist/service/lambda/cfn/event-source-mapping/sim-cfn-lambda-event-source-mapping-property-rules.d.ts +3 -2
  66. package/dist/service/lambda/cfn/event-source-mapping/sim-cfn-lambda-event-source-mapping-property-rules.js +25 -3
  67. package/dist/service/lambda/cfn/function/sim-cfn-lambda-runtime-skip.d.ts +6 -0
  68. package/dist/service/lambda/cfn/function/sim-cfn-lambda-runtime-skip.js +9 -2
  69. package/dist/service/route53/command/create-hosted-zone/sim-route53-zone-id.js +23 -1
  70. package/dist/service/sns/cfn/topic/sim-cfn-sns-topic-creator.js +1 -0
  71. package/dist/service/sns/cfn/topic/sim-cfn-sns-topic-properties.d.ts +9 -4
  72. package/dist/service/sns/cfn/topic/sim-cfn-sns-topic-properties.js +18 -6
  73. package/dist/service/sns/cfn/topic/sim-cfn-sns-topic-property-names.d.ts +10 -0
  74. package/dist/service/sns/cfn/topic/sim-cfn-sns-topic-property-names.js +16 -1
  75. package/dist/service/ssm/cfn/parameter/sim-cfn-ssm-parameter-creator.js +1 -1
  76. package/dist/service/ssm/cfn/parameter/sim-cfn-ssm-parameter-properties.d.ts +11 -6
  77. package/dist/service/ssm/cfn/parameter/sim-cfn-ssm-parameter-properties.js +18 -9
  78. package/docs/README.md +33 -4
  79. package/docs/ai-skill/README.md +52 -54
  80. package/docs/cli/README.md +84 -94
  81. package/docs/factories/README.md +42 -54
  82. package/docs/lint/README.md +41 -67
  83. package/docs/non-aws-dependencies/README.md +72 -168
  84. package/docs/sdk/README.md +97 -98
  85. package/docs/serve/README.md +192 -898
  86. package/docs/services/cloudformation/README.md +88 -23
  87. package/docs/services/cloudfront/README.md +109 -0
  88. package/docs/services/dynamodb/README.md +128 -8
  89. package/docs/services/eventbridge/README.md +10 -2
  90. package/docs/services/iam/README.md +125 -2
  91. package/docs/services/lambda/README.md +7 -1
  92. package/docs/services/route53/README.md +11 -0
  93. package/docs/services/sns/README.md +13 -7
  94. package/docs/services/ssm/README.md +6 -4
  95. package/docs/terraform/README.md +108 -126
  96. package/docs/time/README.md +80 -120
  97. package/llms.txt +1 -1
  98. package/package.json +1 -1
  99. package/dist/service/iam/cfn/sim-cfn-iam-optional-string.js +0 -17
@@ -1055,8 +1055,15 @@ simulated CloudFront hostname, such as `e123example.cloudfront.net`.
1055
1055
  #### Values from a skipped Resource
1056
1056
 
1057
1057
  A Resource that was skipped, because its type is outside the simulation or because there is no
1058
- simulated Resource to create at all, still answers both intrinsics. `Ref` returns the logical ID, and
1059
- `Fn::GetAtt` returns the string `<logical ID>.<attribute name>`.
1058
+ simulated Resource to create at all, still answers both intrinsics.
1059
+
1060
+ What `Ref` answers with follows how far the refusal got. A service that had already worked out the
1061
+ Resource's name answers with that name. That is the name real CloudFormation would have produced,
1062
+ and the simulation holds it whether or not the Resource behind it was created. Sim Lambda declining
1063
+ a function on its `Runtime` is the case to know about, and it covers CDK's own Python providers. A
1064
+ Resource refused before any name existed answers with the logical ID.
1065
+
1066
+ `Fn::GetAtt` answers with the string `<logical ID>.<attribute name>` for either of them.
1060
1067
 
1061
1068
  ```typescript sim-cloudformation-skipped-resource-values
1062
1069
  /**
@@ -1071,28 +1078,31 @@ const stack = await simAws.cloudFormation().deployTemplate({
1071
1078
  stackName: "stand-in-stack",
1072
1079
  template: {
1073
1080
  Resources: {
1074
- AlarmRule: {
1075
- Type: "AWS::CloudWatch::Alarm",
1081
+ SearchApi: {
1082
+ Type: "AWS::AppSync::GraphQLApi",
1083
+ Properties: {
1084
+ Name: "search",
1085
+ },
1076
1086
  },
1077
1087
  },
1078
1088
  Outputs: {
1079
- AlarmRef: { Value: { Ref: "AlarmRule" } },
1080
- AlarmArn: { Value: { "Fn::GetAtt": ["AlarmRule", "Arn"] } },
1089
+ SearchApiRef: { Value: { Ref: "SearchApi" } },
1090
+ SearchApiArn: { Value: { "Fn::GetAtt": ["SearchApi", "Arn"] } },
1081
1091
  },
1082
1092
  },
1083
1093
  });
1084
1094
 
1085
1095
  await stack.waitForDeployComplete();
1086
1096
 
1087
- console.log(stack.output("AlarmRef"));
1088
- // "AlarmRule"
1097
+ console.log(stack.output("SearchApiRef"));
1098
+ // "SearchApi"
1089
1099
 
1090
- console.log(stack.output("AlarmArn"));
1091
- // "AlarmRule.Arn"
1100
+ console.log(stack.output("SearchApiArn"));
1101
+ // "SearchApi.Arn"
1092
1102
 
1093
1103
  for (const skipped of stack.skippedResources) {
1094
1104
  console.log(skipped.logicalId, skipped.skippedReason);
1095
- // "AlarmRule Unsupported sim CloudFormation Resource service CloudWatch"
1105
+ // "SearchApi Unsupported sim CloudFormation Resource service AppSync"
1096
1106
  }
1097
1107
  ```
1098
1108
 
@@ -1101,7 +1111,7 @@ every Resource holding a `Ref` or `Fn::GetAtt` to a skipped Resource would fail
1101
1111
  every Resource depending on those, until one EventBridge rule took the whole stack down with it. The skip
1102
1112
  stays where it happened.
1103
1113
 
1104
- A stand-in is deliberately shaped unlike an ARN. It fails closed wherever the simulator reads it.
1114
+ A logical ID is deliberately shaped unlike an ARN. It fails closed wherever the simulator reads it.
1105
1115
 
1106
1116
  - In an IAM policy `Resource` it matches no ARN, so a caller relying on that statement is denied.
1107
1117
  - In a property that is parsed as an ARN it is refused as malformed, and that Resource fails.
@@ -1112,9 +1122,58 @@ A stand-in is deliberately shaped unlike an ARN. It fails closed wherever the si
1112
1122
  SDK call fails the way a call for a missing resource does. A `PutItem` naming the skipped table
1113
1123
  gets `ResourceNotFoundException: No DynamoDB Table named Orders`.
1114
1124
 
1115
- A stand-in stands in for something absent, and is never a value to rely on. A test asserting against
1116
- one is asserting on a Resource that was never created. `stack.skippedResources` is where to find out
1117
- which Resources those are and why, under
1125
+ A generated name behaves differently, and is meant to. It is the name an account would have held, so
1126
+ anything reading it reads a well-formed name. CDK writes a function's log group name as
1127
+ `/aws/lambda/` joined to a `Ref` of the function, and the log group is created under
1128
+ `/aws/lambda/<stack>-<logical ID>-<suffix>` even where the function was declined. A deploy Role
1129
+ scoped to `<stack>-*` and `/aws/lambda/<stack>-*` covers the deployment, the way it does on AWS.
1130
+
1131
+ ```typescript sim-cloudformation-skipped-resource-generated-name
1132
+ /**
1133
+ * A function skipped on its Runtime, and the Log Group named after it.
1134
+ */
1135
+
1136
+ import { SimAws } from "@kensio/yulin";
1137
+
1138
+ const simAws = new SimAws();
1139
+
1140
+ const stack = await simAws.cloudFormation().deployTemplate({
1141
+ stackName: "ReportStack",
1142
+ template: {
1143
+ Resources: {
1144
+ ReportFunction: {
1145
+ Type: "AWS::Lambda::Function",
1146
+ Properties: {
1147
+ Role: "arn:aws:iam::111111111111:role/ReportRole",
1148
+ Handler: "index.handler",
1149
+ Runtime: "python3.13",
1150
+ Code: { ZipFile: "def handler(event, context): return 'report'" },
1151
+ },
1152
+ },
1153
+ ReportFunctionLogGroup: {
1154
+ Type: "AWS::Logs::LogGroup",
1155
+ Properties: {
1156
+ LogGroupName: {
1157
+ "Fn::Join": ["", ["/aws/lambda/", { Ref: "ReportFunction" }]],
1158
+ },
1159
+ },
1160
+ },
1161
+ },
1162
+ },
1163
+ });
1164
+
1165
+ await stack.waitForDeployComplete();
1166
+
1167
+ console.log(stack.getResource("ReportFunction")?.refValue);
1168
+ // "ReportStack-ReportFunction-42d643ca8338"
1169
+
1170
+ console.log(simAws.logs().allLogGroups()[0]?.logGroupName);
1171
+ // "/aws/lambda/ReportStack-ReportFunction-42d643ca8338"
1172
+ ```
1173
+
1174
+ The Resource is absent all the same, and the name is no evidence that it is there. Invoking the
1175
+ function above fails with `ResourceNotFoundException`. `stack.skippedResources` is where to find out
1176
+ which Resources were left out and why, under
1118
1177
  [Inspecting stacks and resources](#inspecting-stacks-and-resources).
1119
1178
 
1120
1179
  ### `Fn::Join`
@@ -3976,8 +4035,11 @@ const stack = await simAws.cloudFormation().deployTemplate({
3976
4035
  BucketName: "skipped-site-bucket",
3977
4036
  },
3978
4037
  },
3979
- AlarmRule: {
3980
- Type: "AWS::CloudWatch::Alarm",
4038
+ SearchApi: {
4039
+ Type: "AWS::AppSync::GraphQLApi",
4040
+ Properties: {
4041
+ Name: "search",
4042
+ },
3981
4043
  },
3982
4044
  },
3983
4045
  },
@@ -3986,10 +4048,10 @@ const stack = await simAws.cloudFormation().deployTemplate({
3986
4048
  await stack.waitForDeployComplete();
3987
4049
 
3988
4050
  console.log(stack.skippedResources.map((resource) => resource.logicalId));
3989
- // ["AlarmRule"]
4051
+ // ["SearchApi"]
3990
4052
 
3991
- console.log(stack.getResource("AlarmRule")?.skippedReason);
3992
- // "Unsupported sim CloudFormation Resource service CloudWatch"
4053
+ console.log(stack.getResource("SearchApi")?.skippedReason);
4054
+ // "Unsupported sim CloudFormation Resource service AppSync"
3993
4055
  ```
3994
4056
 
3995
4057
  A skipped Resource is still there for `stack.getResource(...)`, and still answers `Ref` and `Fn::GetAtt` with
@@ -4033,8 +4095,11 @@ const stack = await simAws.cloudFormation().deployTemplate({
4033
4095
  Description: "/opt/awscli/aws",
4034
4096
  },
4035
4097
  },
4036
- AlarmRule: {
4037
- Type: "AWS::CloudWatch::Alarm",
4098
+ SearchApi: {
4099
+ Type: "AWS::AppSync::GraphQLApi",
4100
+ Properties: {
4101
+ Name: "search",
4102
+ },
4038
4103
  },
4039
4104
  },
4040
4105
  },
@@ -4043,7 +4108,7 @@ const stack = await simAws.cloudFormation().deployTemplate({
4043
4108
  await stack.waitForDeployComplete();
4044
4109
 
4045
4110
  console.log(stack.skippedResources.map((resource) => resource.logicalId));
4046
- // ["AlarmRule"]
4111
+ // ["SearchApi"]
4047
4112
 
4048
4113
  console.log(stack.inertResources.map((resource) => resource.logicalId));
4049
4114
  // ["AwsCliLayer"]
@@ -259,6 +259,11 @@ with a 200 for a URL the Bucket has no object for. It is one of the same error c
259
259
  CloudFront allows. Where the response page is itself missing, the viewer gets the status from
260
260
  fetching it, as in CloudFront.
261
261
 
262
+ Behind an origin access control, a response page the Bucket does not hold answers 403 where a public
263
+ Bucket answers 404. The Bucket policy an origin access control is written with grants `s3:GetObject`
264
+ and no `s3:ListBucket`, and S3 refuses a key whose absence it may not report. A test whose Bucket
265
+ was never loaded with the site's error page sees that refusal in place of the page.
266
+
262
267
  A viewer-response function never sees a custom error page. CloudFront runs no viewer-response
263
268
  function once the Origin has answered 400 or higher, and simulated CloudFront does the same, for a
264
269
  CloudFront Function and a Lambda@Edge function alike. The status the Origin returned is what decides
@@ -3781,6 +3786,110 @@ A CDK stack needs no hand-editing.
3781
3786
  other store. Handing back an empty store would let a Function that lost its association run to
3782
3787
  completion and quietly take every default.
3783
3788
 
3789
+ ## Permissions
3790
+
3791
+ Every CloudFront command goes through simulated IAM. So does every CloudFront Resource a
3792
+ CloudFormation Stack creates, decided as the principal the deployment runs as. Stand a project's own
3793
+ execution policy up as a deploy Role and the Stack finds out what the policy leaves out. A
3794
+ deployment naming no principal is decided as the Account root.
3795
+
3796
+ The action is the `cloudfront:` name of the operation. A Distribution action is decided against
3797
+ `arn:aws:cloudfront::<account>:distribution/<id>`, a Function action against `function/<name>` and a
3798
+ key value store action against `key-value-store/<id>`. A create action has nothing to name until it
3799
+ succeeds and is decided against `*`. A policy granting one has to write the wildcard.
3800
+
3801
+ These are the actions the simulation asks about:
3802
+
3803
+ - `CreateDistribution`, `GetDistribution`, `UpdateDistribution` and `DeleteDistribution`
3804
+ - `CreateFunction`, `ListFunctions`, `DescribeFunction`, `GetFunction` and `DeleteFunction`
3805
+ - `CreateInvalidation`, `GetInvalidation` and `ListInvalidations`
3806
+ - `CreateKeyValueStore`, `ListKeyValueStores`, `DescribeKeyValueStore`, `UpdateKeyValueStore` and
3807
+ `DeleteKeyValueStore`, alongside the data API's own `cloudfront-keyvaluestore:` actions on the
3808
+ keys inside a store
3809
+ - `CreateCachePolicy` and `DeleteCachePolicy`
3810
+ - `CreateOriginRequestPolicy` and `DeleteOriginRequestPolicy`
3811
+ - `CreateResponseHeadersPolicy` and `DeleteResponseHeadersPolicy`
3812
+ - `CreateOriginAccessControl` and `DeleteOriginAccessControl`
3813
+
3814
+ The last four pairs reach IAM through CloudFormation alone, since a template is the only way to make
3815
+ one of those four here. Each delete is decided against the ARN of the thing it names, such as
3816
+ `arn:aws:cloudfront::<account>:cache-policy/<id>`.
3817
+
3818
+ ```typescript sim-cloudfront-deploy-permissions
3819
+ /**
3820
+ * A deploy Role refused the cache policy its Stack declares.
3821
+ */
3822
+
3823
+ import { SimAws } from "@kensio/yulin";
3824
+
3825
+ const simAws = new SimAws();
3826
+
3827
+ // A deploy Role allowed CloudFormation and S3, and no CloudFront action.
3828
+ const { Role } = await simAws.iam().createRole({
3829
+ input: {
3830
+ RoleName: "DeployRole",
3831
+ AssumeRolePolicyDocument: JSON.stringify({
3832
+ Version: "2012-10-17",
3833
+ Statement: [
3834
+ {
3835
+ Effect: "Allow",
3836
+ Principal: { Service: "cloudformation.amazonaws.com" },
3837
+ Action: "sts:AssumeRole",
3838
+ },
3839
+ ],
3840
+ }),
3841
+ },
3842
+ });
3843
+
3844
+ await simAws.iam().putRolePolicy({
3845
+ input: {
3846
+ RoleName: "DeployRole",
3847
+ PolicyName: "DeployPolicy",
3848
+ PolicyDocument: JSON.stringify({
3849
+ Version: "2012-10-17",
3850
+ Statement: [
3851
+ {
3852
+ Effect: "Allow",
3853
+ Action: ["cloudformation:*", "s3:*"],
3854
+ Resource: "*",
3855
+ },
3856
+ ],
3857
+ }),
3858
+ },
3859
+ });
3860
+
3861
+ try {
3862
+ await simAws.cloudFormation().deployTemplate({
3863
+ stackName: "site-stack",
3864
+ caller: { kind: "arn", arn: Role.Arn },
3865
+ template: {
3866
+ Resources: {
3867
+ SiteCachePolicy: {
3868
+ Type: "AWS::CloudFront::CachePolicy",
3869
+ Properties: {
3870
+ CachePolicyConfig: {
3871
+ Name: "site-caching",
3872
+ MinTTL: 0,
3873
+ ParametersInCacheKeyAndForwardedToOrigin: {
3874
+ EnableAcceptEncodingGzip: false,
3875
+ CookiesConfig: { CookieBehavior: "none" },
3876
+ HeadersConfig: { HeaderBehavior: "none" },
3877
+ QueryStringsConfig: { QueryStringBehavior: "none" },
3878
+ },
3879
+ },
3880
+ },
3881
+ },
3882
+ },
3883
+ },
3884
+ });
3885
+ } catch (error) {
3886
+ // Sim CloudFormation Resource SiteCachePolicy creation failed: User:
3887
+ // arn:aws:iam::...:role/DeployRole is not authorized to perform:
3888
+ // cloudfront:CreateCachePolicy on resource: *
3889
+ console.log((error as Error).message);
3890
+ }
3891
+ ```
3892
+
3784
3893
  ## Available functionality
3785
3894
 
3786
3895
  Sim CloudFront currently supports:
@@ -2443,6 +2443,123 @@ console.log(output.Responses[1]); // {}
2443
2443
  That is the difference from `BatchGetItem`, which leaves a missing item out of its answer
2444
2444
  altogether. Here the answers stay lined up with the Gets that asked for them.
2445
2445
 
2446
+ ### Transactions through the document client
2447
+
2448
+ `TransactWriteCommand` and `TransactGetCommand` from `@aws-sdk/lib-dynamodb` reach the same two
2449
+ operations. Every action's `Item`, `Key` and `ExpressionAttributeValues` are converted on the way in,
2450
+ and the `Item` of each `Responses` entry on the way out.
2451
+
2452
+ ```typescript sim-dynamodb-document-transactions
2453
+ /**
2454
+ * Writing an order and the claim on its code together, in plain JavaScript.
2455
+ */
2456
+
2457
+ import { CreateTableCommand, DynamoDBClient } from "@aws-sdk/client-dynamodb";
2458
+ import {
2459
+ DynamoDBDocumentClient,
2460
+ TransactGetCommand,
2461
+ TransactWriteCommand,
2462
+ } from "@aws-sdk/lib-dynamodb";
2463
+
2464
+ import { SimSdk } from "@kensio/yulin/sdk";
2465
+
2466
+ using simSdk = new SimSdk();
2467
+
2468
+ const documents = DynamoDBDocumentClient.from(
2469
+ new DynamoDBClient({ region: "eu-west-2" }),
2470
+ );
2471
+ simSdk.intercept(documents);
2472
+
2473
+ for (const [tableName, keyName] of [
2474
+ ["OrdersTable", "orderId"],
2475
+ ["ClaimsTable", "code"],
2476
+ ] as const) {
2477
+ await documents.send(
2478
+ new CreateTableCommand({
2479
+ TableName: tableName,
2480
+ KeySchema: [{ AttributeName: keyName, KeyType: "HASH" }],
2481
+ AttributeDefinitions: [{ AttributeName: keyName, AttributeType: "S" }],
2482
+ BillingMode: "PAY_PER_REQUEST",
2483
+ }),
2484
+ );
2485
+ }
2486
+ await simSdk.simAws.backgroundTasksComplete();
2487
+
2488
+ const claim = {
2489
+ TableName: "ClaimsTable",
2490
+ ConditionExpression: "attribute_not_exists(code)",
2491
+ };
2492
+
2493
+ // The order and the claim on its code go in together.
2494
+ await documents.send(
2495
+ new TransactWriteCommand({
2496
+ TransactItems: [
2497
+ {
2498
+ Put: {
2499
+ TableName: "OrdersTable",
2500
+ Item: { orderId: "order-1", total: 42, lines: [{ sku: "widget" }] },
2501
+ },
2502
+ },
2503
+ { Put: { ...claim, Item: { code: "ABC123", orderId: "order-1" } } },
2504
+ ],
2505
+ }),
2506
+ );
2507
+
2508
+ const read = await documents.send(
2509
+ new TransactGetCommand({
2510
+ TransactItems: [
2511
+ { Get: { TableName: "OrdersTable", Key: { orderId: "order-1" } } },
2512
+ { Get: { TableName: "ClaimsTable", Key: { code: "ABC123" } } },
2513
+ ],
2514
+ }),
2515
+ );
2516
+
2517
+ const lines = read.Responses?.[0]?.Item?.["lines"] as { sku: string }[];
2518
+ console.log(lines[0]?.sku); // widget
2519
+ console.log(read.Responses?.[1]?.Item?.["orderId"]); // order-1
2520
+
2521
+ // A second order wanting the same code loses the claim, and loses the order
2522
+ // with it.
2523
+ try {
2524
+ await documents.send(
2525
+ new TransactWriteCommand({
2526
+ TransactItems: [
2527
+ {
2528
+ Put: {
2529
+ TableName: "OrdersTable",
2530
+ Item: { orderId: "order-2", total: 7 },
2531
+ },
2532
+ },
2533
+ { Put: { ...claim, Item: { code: "ABC123", orderId: "order-2" } } },
2534
+ ],
2535
+ }),
2536
+ );
2537
+ } catch (error) {
2538
+ const cancelled = error as {
2539
+ name: string;
2540
+ CancellationReasons?: { Code: string }[];
2541
+ };
2542
+
2543
+ console.log(cancelled.name); // "TransactionCanceledException"
2544
+ console.log(cancelled.CancellationReasons?.map((reason) => reason.Code));
2545
+ // ["None", "ConditionalCheckFailed"]
2546
+ }
2547
+
2548
+ const second = await documents.send(
2549
+ new TransactGetCommand({
2550
+ TransactItems: [
2551
+ { Get: { TableName: "OrdersTable", Key: { orderId: "order-2" } } },
2552
+ ],
2553
+ }),
2554
+ );
2555
+
2556
+ console.log(second.Responses?.[0]); // {}
2557
+ ```
2558
+
2559
+ `CancellationReasons` arrive as the low-level Command reports them. There is one per action, in
2560
+ `TransactItems` order. A reason's `Item` holds AttributeValues, because a cancelled transaction is
2561
+ thrown, and the real document client converts nothing on the way out of a transactional write.
2562
+
2446
2563
  ## Expiring items with time to live
2447
2564
 
2448
2565
  `UpdateTimeToLive` names the attribute a table expires items by, and `DescribeTimeToLive` reports
@@ -2930,8 +3047,11 @@ console.log(tags.has("priority")); // true
2930
3047
  ```
2931
3048
 
2932
3049
  `PutCommand`, `GetCommand`, `DeleteCommand`, `UpdateCommand`, `QueryCommand`, `ScanCommand`,
2933
- `BatchWriteCommand` and `BatchGetCommand` are converted. A document Command with no route here, such
2934
- as `TransactWriteCommand`, is refused by name before anything tries to convert its values.
3050
+ `BatchWriteCommand`, `BatchGetCommand`, `TransactWriteCommand` and `TransactGetCommand` are
3051
+ converted. The two transactional ones have
3052
+ [a section of their own](#transactions-through-the-document-client). A document Command with no route
3053
+ here, such as the PartiQL `ExecuteStatementCommand`, is refused by name before anything tries to
3054
+ convert its values.
2935
3055
 
2936
3056
  Intercept the document client itself. `DynamoDBDocumentClient.from(client)` builds a separate object
2937
3057
  outside the `DynamoDBClient` class, so intercepting the base client leaves Commands sent through the
@@ -3621,15 +3741,15 @@ Arn`, `Fn::GetAtt … StreamArn` and `Fn::GetAtt … TableId` answering. A CDK `
3621
3741
  - SDK interception, and an intercepted `DynamoDBClient` or `DynamoDBStreamsClient` reaches the
3622
3742
  simulation.
3623
3743
  - The `@aws-sdk/lib-dynamodb` document client, with `PutCommand`, `GetCommand`, `DeleteCommand`,
3624
- `UpdateCommand`, `QueryCommand`, `ScanCommand`, `BatchWriteCommand` and `BatchGetCommand`
3625
- converting native JavaScript values on the way in and out, and `paginateQuery` and `paginateScan`
3626
- paging through a simulated table.
3744
+ `UpdateCommand`, `QueryCommand`, `ScanCommand`, `BatchWriteCommand`, `BatchGetCommand`,
3745
+ `TransactWriteCommand` and `TransactGetCommand` converting native JavaScript values on the way in
3746
+ and out, and `paginateQuery` and `paginateScan` paging through a simulated table.
3627
3747
 
3628
3748
  ## Limitations
3629
3749
 
3630
- - The document client's transaction and PartiQL Commands go unconverted. PartiQL is an operation
3631
- this simulation lacks yet. The transactions are simulated as operations, so only the document form
3632
- of them is missing. Both are refused by name, never half converted.
3750
+ - The document client's PartiQL Commands go unconverted, because PartiQL is an operation this
3751
+ simulation lacks yet. `ExecuteStatementCommand`, `BatchExecuteStatementCommand` and
3752
+ `ExecuteTransactionCommand` are refused by name, never half converted.
3633
3753
  - A document client's translate config goes unread. The marshalling options it was built with do not
3634
3754
  apply. See [the SDK docs](https://yulinsim.dev/sdk/#limitations).
3635
3755
  - `Expected`, `ConditionalOperator`, `AttributeUpdates`, `KeyConditions`, `QueryFilter` and
@@ -919,6 +919,12 @@ at deploy time naming the property and the Resource. The alternative is a rule t
919
919
  event where one field was asked for. Tearing the stack down removes the buses, rules and targets it
920
920
  created.
921
921
 
922
+ `Tags` is the one difference from the commands, which refuse it outright. A template's tags are
923
+ usually the whole stack's (a CDK app calling `Tags.of(app).add(...)` tags every rule and bus in it),
924
+ and a rule routes the same events whether it carries them or not. They are recorded as an ignored
925
+ property and the deploy stands. Nothing reads them back. A rule or a bus deployed with tags behaves
926
+ as though the template had never named them.
927
+
922
928
  ## Reading back a failed delivery
923
929
 
924
930
  Real EventBridge tells the caller nothing about a failed delivery. A `PutEvents` that matched a rule
@@ -1101,7 +1107,8 @@ no permission for.
1101
1107
  - EventBridge Scheduler is a separate service, simulated separately. See
1102
1108
  [simulated Scheduler](https://yulinsim.dev/services/scheduler/).
1103
1109
  - Rule tags, a rule `RoleArn`, managed rules and the `ENABLED_WITH_ALL_CLOUDTRAIL_MANAGEMENT_EVENTS`
1104
- state are all refused.
1110
+ state are all refused at `PutRule`. An `AWS::Events::Rule` carrying `Tags` deploys with the tags
1111
+ dropped and the property recorded.
1105
1112
  - Deleting an event bus deletes its rules, and deleting a rule deletes its targets. Real EventBridge
1106
1113
  refuses to delete either while it still has what hangs off it.
1107
1114
  - `AWS::Events::EventBusPolicy`, `AWS::Events::Archive`, `AWS::Events::Connection` and
@@ -1113,6 +1120,7 @@ no permission for.
1113
1120
  stricter than real AWS.
1114
1121
  - Putting an event onto another account's or region's bus is refused.
1115
1122
  - Partner event buses and partner event sources are refused, as are event bus tags, encryption with
1116
- a customer managed key, dead letter queues, logging configuration and global endpoints.
1123
+ a customer managed key, dead letter queues, logging configuration and global endpoints. An
1124
+ `AWS::Events::EventBus` carrying `Tags` deploys with the tags dropped and the property recorded.
1117
1125
  - Archives, replay, schema registry and discovery, API destinations and connections are not
1118
1126
  simulated.
@@ -260,6 +260,11 @@ from the resolved caller and `aws:RequestedRegion` from the Region the request w
260
260
  service supplies under either name is overwritten by the derived one. Context-key names are matched
261
261
  case-insensitively, while string values remain case-sensitive.
262
262
 
263
+ IAM's own requests carry two service keys. A Role handed to a service carries
264
+ [`iam:PassedToService`](#passing-a-role-to-a-service), and a `CreateRole` request carries the
265
+ boundary it declares as
266
+ [`iam:PermissionsBoundary`](#creating-a-role-under-a-permissions-boundary).
267
+
263
268
  Every simulated service supplies its own Region. A policy conditioned on `aws:RequestedRegion`
264
269
  therefore sees the Region of the service that handled the request, whichever Region the caller was
265
270
  in, and a CloudFormation deployment carries the Region of the Stack it is deploying. IAM, CloudFront
@@ -1045,6 +1050,115 @@ await simAws.backgroundTasksComplete();
1045
1050
  A request naming no caller is decided as the Account root, which may pass any Role. A test that never
1046
1051
  mentions IAM keeps working.
1047
1052
 
1053
+ ## Creating a Role under a permissions boundary
1054
+
1055
+ An account that requires every Role to carry a permissions boundary allows `iam:CreateRole` only
1056
+ under `StringEquals` on `iam:PermissionsBoundary`. Its CloudFormation execution policy is written
1057
+ that way, and the CDK `PermissionsBoundary.fromName(...)` aspect puts the boundary ARN on every Role
1058
+ the app synthesizes. A `CreateRoleCommand` carrying `PermissionsBoundary` supplies the key, and
1059
+ `AWS::IAM::Role` supplies it from the template property of the same name.
1060
+
1061
+ A request that leaves the property out leaves the key unset. A positive operator over an unset key
1062
+ matches nothing, and the guard refuses the Role. A request naming some other policy is refused the
1063
+ same way. A test can assert both refusals, and that is what makes the policy worth standing up as a
1064
+ deploy Role.
1065
+
1066
+ `CreateRole` and `GetRole` then describe the boundary the Role carries, in the attachment shape IAM
1067
+ answers with (`PermissionsBoundaryType` and `PermissionsBoundaryArn`). `ListRoles` leaves the field
1068
+ out, as IAM does, and points a caller at `GetRole`. The boundary is not yet evaluated as a policy
1069
+ source of its own, and does not narrow what the Role may then do
1070
+ (see [Limitations](#limitations)).
1071
+
1072
+ ```typescript sim-iam-permissions-boundary
1073
+ /**
1074
+ * Deploying into an account that requires a permissions boundary.
1075
+ */
1076
+
1077
+ import { GetRoleCommand } from "@aws-sdk/client-iam";
1078
+ import { SimAws } from "@kensio/yulin";
1079
+
1080
+ const simAws = new SimAws({ defaultAccountId: "123456789012" });
1081
+ const boundaryArn = "arn:aws:iam::123456789012:policy/DeveloperBoundary";
1082
+
1083
+ const deployer = await simAws.iam().makeDeployRole({
1084
+ roleName: "cfn-exec",
1085
+ policyDocument: {
1086
+ Version: "2012-10-17",
1087
+ Statement: [
1088
+ {
1089
+ Effect: "Allow",
1090
+ Action: "iam:CreateRole",
1091
+ Resource: "*",
1092
+ Condition: {
1093
+ StringEquals: { "iam:PermissionsBoundary": boundaryArn },
1094
+ },
1095
+ },
1096
+ {
1097
+ Effect: "Allow",
1098
+ Action: ["cloudformation:*", "iam:PutRolePolicy", "iam:PassRole"],
1099
+ Resource: "*",
1100
+ },
1101
+ ],
1102
+ },
1103
+ });
1104
+
1105
+ const jobRoleTrust = {
1106
+ Version: "2012-10-17",
1107
+ Statement: {
1108
+ Effect: "Allow",
1109
+ Action: "sts:AssumeRole",
1110
+ Principal: { Service: "lambda.amazonaws.com" },
1111
+ },
1112
+ };
1113
+
1114
+ const stack = await simAws.cloudFormation().deployTemplate({
1115
+ stackName: "guarded-stack",
1116
+ template: {
1117
+ Resources: {
1118
+ JobRole: {
1119
+ Type: "AWS::IAM::Role",
1120
+ Properties: {
1121
+ RoleName: "JobRole",
1122
+ PermissionsBoundary: boundaryArn,
1123
+ AssumeRolePolicyDocument: jobRoleTrust,
1124
+ },
1125
+ },
1126
+ },
1127
+ },
1128
+ caller: deployer,
1129
+ });
1130
+
1131
+ // CREATE_COMPLETE
1132
+ console.log(stack.getResource("JobRole")?.status);
1133
+
1134
+ const roleRead = await simAws
1135
+ .iam()
1136
+ .getRole(new GetRoleCommand({ RoleName: "JobRole" }));
1137
+
1138
+ // arn:aws:iam::123456789012:policy/DeveloperBoundary
1139
+ console.log(roleRead.Role.PermissionsBoundary?.PermissionsBoundaryArn);
1140
+
1141
+ try {
1142
+ await simAws.cloudFormation().deployTemplate({
1143
+ stackName: "unguarded-stack",
1144
+ template: {
1145
+ Resources: {
1146
+ BareRole: {
1147
+ Type: "AWS::IAM::Role",
1148
+ Properties: {
1149
+ RoleName: "BareRole",
1150
+ AssumeRolePolicyDocument: jobRoleTrust,
1151
+ },
1152
+ },
1153
+ },
1154
+ },
1155
+ caller: deployer,
1156
+ });
1157
+ } catch (error) {
1158
+ console.error("A Role declaring no boundary was refused", error);
1159
+ }
1160
+ ```
1161
+
1048
1162
  ## A Role for a CloudFormation deployment
1049
1163
 
1050
1164
  `makeDeployRole(...)` creates a Role from a policy document and hands back a caller. Pass it as
@@ -1241,6 +1355,11 @@ the logical ID when it is omitted, and inline `Policies` declared on a Role are
1241
1355
  Role's inline policies. The `ReadOnlyAccess` policy above is attached to `LambdaExecutionRole`.
1242
1356
  Authorization for that Role reads the policy document the template gave it.
1243
1357
 
1358
+ A Role's `PermissionsBoundary` reaches the authorization decision the deployment is made under, and
1359
+ is recorded on the Role it names.
1360
+ [Creating a Role under a permissions boundary](#creating-a-role-under-a-permissions-boundary) covers
1361
+ the guard it exists for.
1362
+
1244
1363
  ### Users
1245
1364
 
1246
1365
  An `AWS::IAM::User` creates a User in the Stack's Account. `UserName` names it and falls back to the
@@ -1534,6 +1653,8 @@ Sim IAM currently supports:
1534
1653
  - IAM authorization at simulated service boundaries, such as Route53 actions
1535
1654
  - `iam:PassRole` authorization of a Role handed to simulated Lambda, Scheduler, EventBridge, Step
1536
1655
  Functions, ECS or Firehose, with `iam:PassedToService` supplied
1656
+ - `iam:CreateRole` authorization against `iam:PermissionsBoundary`, from the `PermissionsBoundary`
1657
+ a request or an `AWS::IAM::Role` declares
1537
1658
  - Resolving the caller of an HTTP request, from an `x-sim-aws-caller` header or a verified SigV4
1538
1659
  signature, defaulting to anonymous, and the resource it is made on behalf of from
1539
1660
  `x-sim-aws-source-arn` and `x-sim-aws-source-account`
@@ -1551,8 +1672,10 @@ Sim IAM models the policy behaviour that multi-service tests most commonly need.
1551
1672
 
1552
1673
  - Groups are absent. An `AWS::IAM::User` naming one fails, and an `AWS::IAM::Policy` naming one
1553
1674
  fails too
1554
- - Permissions boundaries and session policies are not evaluated. Service control policies are, and
1555
- are attached through [simulated Organizations](https://yulinsim.dev/services/organizations/ "Simulated Organizations service control policies usage docs")
1675
+ - Permissions boundaries and session policies are not evaluated. A boundary a `CreateRole` request
1676
+ declares is authorized against `iam:PermissionsBoundary` and described by `GetRole`, and stops
1677
+ there. Service control policies are evaluated, and are attached through
1678
+ [simulated Organizations](https://yulinsim.dev/services/organizations/ "Simulated Organizations service control policies usage docs")
1556
1679
  - Managed Policies have a single version, and the policy version commands are absent
1557
1680
  - A policy document is measured against IAM's character limit one document at a time. The
1558
1681
  20,480-character cap on the sum of a User's inline policies is absent, and so is the cap on how
@@ -1702,6 +1702,10 @@ Reporting individual failures can be tested against a made event too. The handle
1702
1702
  a template naming one is refused. The same Resource deploys a
1703
1703
  [stream mapping](#stream-mappings-in-templates), which has to have one.
1704
1704
 
1705
+ `Tags` is the one property recorded rather than refused. A template's tags are usually the whole
1706
+ stack's (a CDK app calling `Tags.of(app).add(...)` tags every mapping in it), and a mapping delivers
1707
+ the same records whether it carries them or not. The deploy stands and nothing reads them back.
1708
+
1705
1709
  ## Triggering a function from a DynamoDB stream
1706
1710
 
1707
1711
  An event source mapping also connects a [simulated table's stream](https://yulinsim.dev/services/dynamodb/#capturing-changes-with-a-stream "Simulated DynamoDB streams docs")
@@ -4268,7 +4272,9 @@ Current documented limitations:
4268
4272
  - SQS queues, DynamoDB streams and Kinesis streams are the only event sources. Kafka, DocumentDB and
4269
4273
  Kinesis enhanced fan-out consumers are refused outright, and so are `FilterCriteria`,
4270
4274
  `ScalingConfig`, `DestinationConfig`, `BisectBatchOnFunctionError`, `ParallelizationFactor`,
4271
- `TumblingWindowInSeconds` and the other mapping inputs this simulation has no behaviour for.
4275
+ `TumblingWindowInSeconds` and the other mapping inputs this simulation has no behaviour for. An
4276
+ `AWS::Lambda::EventSourceMapping` carrying `Tags` is the exception, and deploys with the tags
4277
+ dropped and the property recorded.
4272
4278
  - A failed stream batch waits 1, 2, 4, 8 and 16 seconds between attempts, where AWS documents no
4273
4279
  delay. That is deliberate. A delay of zero falls due at the instant the clock already reads, and a
4274
4280
  handler that always throws would leave `advanceBy` with work falling due forever. A mapping that