@kensio/yulin 1.21.4 → 1.21.6

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 (97) hide show
  1. package/dist/service/cloudformation/cdk/assets/sim-cdk-assets-publisher.d.ts +18 -13
  2. package/dist/service/cloudformation/cdk/assets/sim-cdk-assets-publisher.js +16 -24
  3. package/dist/service/cloudformation/cdk/sim-cdk-cloud-assembly.factory.d.ts +12 -0
  4. package/dist/service/cloudformation/cdk/sim-cdk-cloud-assembly.factory.js +16 -7
  5. package/dist/service/cloudformation/command/create-stack/create-stack.handler.d.ts +3 -0
  6. package/dist/service/cloudformation/command/create-stack/create-stack.handler.js +4 -1
  7. package/dist/service/cloudformation/command/update-stack/update-stack.handler.d.ts +6 -0
  8. package/dist/service/cloudformation/command/update-stack/update-stack.handler.js +7 -1
  9. package/dist/service/cloudformation/deploy/sim-cfn-cdk-out-deployer.js +2 -1
  10. package/dist/service/cloudformation/deploy/sim-cfn-cdk-out-options-deployed.d.ts +10 -0
  11. package/dist/service/cloudformation/deploy/sim-cfn-cdk-out-options-deployed.js +14 -0
  12. package/dist/service/cloudformation/deploy/sim-cfn-cdk-out-plan.d.ts +11 -1
  13. package/dist/service/cloudformation/deploy/sim-cfn-cdk-out-plan.js +3 -2
  14. package/dist/service/cloudformation/deploy/sim-cfn-cdk-out-stack-options.d.ts +5 -8
  15. package/dist/service/cloudformation/deploy/sim-cfn-cdk-out-stack-options.js +0 -14
  16. package/dist/service/cloudformation/deploy/sim-cfn-template-deployer.d.ts +9 -0
  17. package/dist/service/cloudformation/deploy/sim-cfn-template-deployer.js +2 -0
  18. package/dist/service/cloudformation/deploy/sim-cfn-template-file-loader.d.ts +10 -0
  19. package/dist/service/cloudformation/deploy/sim-cfn-template-file-loader.js +2 -1
  20. package/dist/service/cloudformation/deploy/sim-cfn-template-file-updater.js +1 -0
  21. package/dist/service/cloudformation/resource/cfn/sim-cfn-default-resource-value-adapter.d.ts +8 -1
  22. package/dist/service/cloudformation/resource/cfn/sim-cfn-default-resource-value-adapter.js +9 -2
  23. package/dist/service/cloudformation/resource/cfn/sim-cfn-resource-value-adapter.d.ts +6 -1
  24. package/dist/service/cloudformation/resource/cfn/sim-cfn-resource-value-adapter.js +2 -1
  25. package/dist/service/cloudformation/resource/create/sim-cfn-resource-create-operation.d.ts +4 -0
  26. package/dist/service/cloudformation/resource/create/sim-cfn-resource-create-operation.js +6 -0
  27. package/dist/service/cloudformation/resource/sim-cfn-resource-record.d.ts +19 -0
  28. package/dist/service/cloudformation/resource/sim-cfn-resource-record.js +26 -0
  29. package/dist/service/cloudformation/resource/sim-cfn-resource.js +1 -0
  30. package/dist/service/cloudformation/resource/unsupported/sim-cfn-named-skip.d.ts +26 -0
  31. package/dist/service/cloudformation/resource/unsupported/sim-cfn-named-skip.js +31 -0
  32. package/dist/service/cloudformation/stack/sim-cfn-stack-deploy-context.d.ts +47 -0
  33. package/dist/service/cloudformation/stack/sim-cfn-stack-deploy-context.js +46 -0
  34. package/dist/service/cloudformation/stack/sim-cfn-stack-resource-operations.d.ts +9 -19
  35. package/dist/service/cloudformation/stack/sim-cfn-stack-resource-operations.js +12 -26
  36. package/dist/service/cloudformation/stack/sim-cfn-stack.js +2 -1
  37. package/dist/service/cloudformation/stack/sim-cfn-stack.type.d.ts +14 -0
  38. package/dist/service/cloudfront/cfn/cache-policy/sim-cfn-cf-cache-policy-creator.d.ts +5 -2
  39. package/dist/service/cloudfront/cfn/cache-policy/sim-cfn-cf-cache-policy-creator.js +10 -2
  40. package/dist/service/cloudfront/cfn/origin-access-control/sim-cfn-cf-oac-creator.d.ts +5 -2
  41. package/dist/service/cloudfront/cfn/origin-access-control/sim-cfn-cf-oac-creator.js +10 -2
  42. package/dist/service/cloudfront/cfn/origin-request-policy/sim-cfn-cf-orp-creator.d.ts +5 -2
  43. package/dist/service/cloudfront/cfn/origin-request-policy/sim-cfn-cf-orp-creator.js +10 -2
  44. package/dist/service/cloudfront/cfn/policy/sim-cfn-cf-policy-creators.d.ts +33 -0
  45. package/dist/service/cloudfront/cfn/policy/sim-cfn-cf-policy-creators.js +39 -0
  46. package/dist/service/cloudfront/cfn/response-headers-policy/sim-cfn-cf-rh-policy-creator.d.ts +5 -2
  47. package/dist/service/cloudfront/cfn/response-headers-policy/sim-cfn-cf-rh-policy-creator.js +10 -2
  48. package/dist/service/cloudfront/cfn/sim-cfn-cf-authorizer.d.ts +40 -0
  49. package/dist/service/cloudfront/cfn/sim-cfn-cf-authorizer.js +50 -0
  50. package/dist/service/cloudfront/cfn/sim-cfn-cloudfront-resource-factory.d.ts +1 -4
  51. package/dist/service/cloudfront/cfn/sim-cfn-cloudfront-resource-factory.js +14 -46
  52. package/dist/service/cloudfront/sim-cloudfront-commands.d.ts +6 -0
  53. package/dist/service/cloudfront/sim-cloudfront-commands.js +7 -0
  54. package/dist/service/cloudfront/sim-cloudfront.d.ts +6 -0
  55. package/dist/service/cloudfront/sim-cloudfront.js +7 -0
  56. package/dist/service/dynamodb/document/sim-dynamodb-document-command-paths.d.ts +8 -0
  57. package/dist/service/dynamodb/document/sim-dynamodb-document-command-paths.js +62 -0
  58. package/dist/service/dynamodb/document/sim-dynamodb-document-routes.d.ts +1 -1
  59. package/dist/service/dynamodb/document/sim-dynamodb-document-routes.js +3 -1
  60. package/dist/service/iam/authorize/sim-iam-action-authorizer.d.ts +8 -3
  61. package/dist/service/iam/authorize/sim-iam-action-authorizer.js +13 -4
  62. package/dist/service/iam/cfn/role/sim-cfn-iam-role-creator.js +1 -0
  63. package/dist/service/iam/cfn/role/sim-cfn-iam-role-properties-parser.d.ts +1 -0
  64. package/dist/service/iam/cfn/role/sim-cfn-iam-role-properties-parser.js +21 -14
  65. package/dist/service/iam/cfn/{sim-cfn-iam-optional-string.d.ts → sim-cfn-iam-property.d.ts} +10 -2
  66. package/dist/service/iam/cfn/sim-cfn-iam-property.js +31 -0
  67. package/dist/service/iam/cfn/user/sim-cfn-iam-user-properties-parser.js +1 -1
  68. package/dist/service/iam/command/role/create-role/create-role-record-factory.js +3 -0
  69. package/dist/service/iam/command/role/create-role/create-role.command.d.ts +10 -0
  70. package/dist/service/iam/command/role/get-role/get-role.command.d.ts +2 -0
  71. package/dist/service/iam/command/role/get-role/get-role.handler.js +2 -0
  72. package/dist/service/iam/command/role/sim-iam-role-command-handlers.js +15 -1
  73. package/dist/service/iam/role/sim-iam-role-boundary.d.ts +24 -0
  74. package/dist/service/iam/role/sim-iam-role-boundary.js +24 -0
  75. package/dist/service/iam/role/sim-iam-role-with-policy.factory.d.ts +1 -0
  76. package/dist/service/iam/role/sim-iam-role.d.ts +10 -0
  77. package/dist/service/iam/serve/sim-iam-query-entity.d.ts +6 -1
  78. package/dist/service/iam/serve/sim-iam-query-entity.js +10 -4
  79. package/dist/service/iam/serve/sim-iam-query-role-operations.js +15 -3
  80. package/dist/service/lambda/cfn/function/sim-cfn-lambda-runtime-skip.d.ts +6 -0
  81. package/dist/service/lambda/cfn/function/sim-cfn-lambda-runtime-skip.js +9 -2
  82. package/dist/service/route53/command/create-hosted-zone/sim-route53-zone-id.js +23 -1
  83. package/dist/service/s3/bucket/sim-s3-bucket-maker.d.ts +31 -0
  84. package/dist/service/s3/bucket/sim-s3-bucket-maker.js +41 -0
  85. package/dist/service/s3/command/create-bucket/create-bucket.handler.d.ts +2 -2
  86. package/dist/service/s3/command/create-bucket/create-bucket.handler.js +4 -12
  87. package/dist/service/s3/sim-s3-commands.d.ts +6 -0
  88. package/dist/service/s3/sim-s3-commands.js +7 -0
  89. package/dist/service/s3/sim-s3.d.ts +16 -0
  90. package/dist/service/s3/sim-s3.js +25 -0
  91. package/docs/services/cloudformation/README.md +144 -25
  92. package/docs/services/cloudfront/README.md +109 -0
  93. package/docs/services/dynamodb/README.md +128 -8
  94. package/docs/services/iam/README.md +125 -2
  95. package/docs/services/route53/README.md +11 -0
  96. package/package.json +2 -2
  97. package/dist/service/iam/cfn/sim-cfn-iam-optional-string.js +0 -17
@@ -18,9 +18,25 @@ import type { SimS3MountFilesystemOptions } from "./mount/sim-s3-mount.type.js";
18
18
  */
19
19
  export declare class SimS3 extends SimS3Operations {
20
20
  private readonly bucketAccess;
21
+ private readonly bucketMaker;
21
22
  private readonly cfnFactory;
22
23
  private readonly sdkRouter;
23
24
  constructor(properties?: SimS3Properties);
25
+ /**
26
+ * Make a Bucket in this scope without a `CreateBucketCommand`, and so
27
+ * without a principal to authorize.
28
+ *
29
+ * This is for a Bucket real AWS has provisioned before the code under test
30
+ * runs, which nothing in the simulation is deploying. The CDK bootstrap
31
+ * staging Bucket is the one the simulator makes for itself: `cdk deploy`
32
+ * publishes assets into a Bucket the bootstrap stack created, and a
33
+ * deployment that had to create it would be charged permissions no real
34
+ * deployment needs.
35
+ *
36
+ * A name this scope cannot take is reported the way `CreateBucketCommand`
37
+ * reports it.
38
+ */
39
+ makeSimBucket(bucketName: SimS3BucketName | string): SimS3Bucket;
24
40
  /**
25
41
  * Change how many keys one page of an Object listing holds, which real S3
26
42
  * fixes at a thousand.
@@ -1,5 +1,6 @@
1
1
  import { SimS3GlobalRegistry } from "./sim-s3-global-registry.js";
2
2
  import { SimS3BucketAccess } from "./bucket/sim-s3-bucket-access.js";
3
+ import { SimS3BucketMaker } from "./bucket/sim-s3-bucket-maker.js";
3
4
  import { SimS3CloudFormationResourceFactory } from "./cfn/sim-cfn-s3-resource-factory.js";
4
5
  import { simAwsAccountRegionScopeFactory } from "../aws/sim-aws-account-region-scope.factory.js";
5
6
  import { SimS3Commands } from "./sim-s3-commands.js";
@@ -16,6 +17,7 @@ import { SimS3SdkCommandRouter } from "./sdk/sim-s3-sdk-command-router.js";
16
17
  */
17
18
  export class SimS3 extends SimS3Operations {
18
19
  bucketAccess;
20
+ bucketMaker;
19
21
  cfnFactory = new SimS3CloudFormationResourceFactory(this);
20
22
  sdkRouter = new SimS3SdkCommandRouter(this);
21
23
  constructor(properties = {}) {
@@ -32,6 +34,29 @@ export class SimS3 extends SimS3Operations {
32
34
  accountRegionScope,
33
35
  s3GlobalRegistry,
34
36
  });
37
+ this.bucketMaker = new SimS3BucketMaker({
38
+ accountRegionScope,
39
+ buckets,
40
+ s3GlobalRegistry,
41
+ background: this.commands.background,
42
+ });
43
+ }
44
+ /**
45
+ * Make a Bucket in this scope without a `CreateBucketCommand`, and so
46
+ * without a principal to authorize.
47
+ *
48
+ * This is for a Bucket real AWS has provisioned before the code under test
49
+ * runs, which nothing in the simulation is deploying. The CDK bootstrap
50
+ * staging Bucket is the one the simulator makes for itself: `cdk deploy`
51
+ * publishes assets into a Bucket the bootstrap stack created, and a
52
+ * deployment that had to create it would be charged permissions no real
53
+ * deployment needs.
54
+ *
55
+ * A name this scope cannot take is reported the way `CreateBucketCommand`
56
+ * reports it.
57
+ */
58
+ makeSimBucket(bucketName) {
59
+ return this.bucketMaker.make(bucketName);
35
60
  }
36
61
  /**
37
62
  * Change how many keys one page of an Object listing holds, which real S3
@@ -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`
@@ -2115,8 +2174,62 @@ const stacks = await simAws.cloudFormation().deployCdkOut({
2115
2174
  console.log(stacks.get("PipelineStack")?.stackName);
2116
2175
  ```
2117
2176
 
2118
- A staged CDK asset is published under the same principal, before CloudFormation reads the template
2119
- that points at it.
2177
+ ### The principal a CDK asset is published as
2178
+
2179
+ A real `cdk deploy` runs in two phases under two Roles. `cdk-assets` publishes every staged file
2180
+ asset to the bootstrap staging bucket as the file publishing Role, and CloudFormation then processes
2181
+ the template as the execution Role. `assetsCaller` names the first of those, beside the `caller`
2182
+ naming the second.
2183
+
2184
+ ```typescript sim-cloudformation-assets-caller
2185
+ /**
2186
+ * Publishing a Stack's CDK file assets as the file publishing Role.
2187
+ */
2188
+
2189
+ import { SimAws } from "@kensio/yulin";
2190
+
2191
+ const simAws = new SimAws({
2192
+ defaultAccountId: "123456789012",
2193
+ defaultRegionName: "eu-west-2",
2194
+ });
2195
+
2196
+ const publisher = await simAws.iam().makeDeployRole({
2197
+ roleName: "cdk-file-publishing",
2198
+ policyDocument: {
2199
+ Version: "2012-10-17",
2200
+ Statement: {
2201
+ Effect: "Allow",
2202
+ Action: ["s3:GetObject*", "s3:GetBucket*", "s3:List*", "s3:PutObject"],
2203
+ Resource: "arn:aws:s3:::cdk-hnb659fds-assets-*",
2204
+ },
2205
+ },
2206
+ });
2207
+
2208
+ const executor = await simAws.iam().makeDeployRole({
2209
+ roleName: "cdk-exec",
2210
+ policyDocument: {
2211
+ Version: "2012-10-17",
2212
+ Statement: { Effect: "Allow", Action: "s3:*", Resource: "*" },
2213
+ },
2214
+ });
2215
+
2216
+ const stacks = await simAws.cloudFormation().deployCdkOut({
2217
+ directoryPath: "cdk.out",
2218
+ caller: executor,
2219
+ assetsCaller: publisher,
2220
+ });
2221
+
2222
+ console.log(stacks.size);
2223
+ ```
2224
+
2225
+ `deployTemplate(...)` and `deployTemplateFile(...)` take the same option, and a Stack in an assembly
2226
+ names its own in `stackOptions`. A deployment that names no `assetsCaller` publishes its assets as
2227
+ `caller`, the way every deployment did before the two could be told apart.
2228
+
2229
+ The CDK bootstrap stack provisions the staging bucket, long before either Role runs. Yulin stands in
2230
+ for that and makes the bucket outside IAM when the first asset is published into it. So a Role
2231
+ holding only what a scoped execution policy grants on `cdk-hnb659fds-assets-*` deploys a cloud
2232
+ assembly with file assets, with no `s3:CreateBucket` or `s3:PutObject` added to it.
2120
2233
 
2121
2234
  ## Editing a synthesized template before deploying it
2122
2235
 
@@ -3922,8 +4035,11 @@ const stack = await simAws.cloudFormation().deployTemplate({
3922
4035
  BucketName: "skipped-site-bucket",
3923
4036
  },
3924
4037
  },
3925
- AlarmRule: {
3926
- Type: "AWS::CloudWatch::Alarm",
4038
+ SearchApi: {
4039
+ Type: "AWS::AppSync::GraphQLApi",
4040
+ Properties: {
4041
+ Name: "search",
4042
+ },
3927
4043
  },
3928
4044
  },
3929
4045
  },
@@ -3932,10 +4048,10 @@ const stack = await simAws.cloudFormation().deployTemplate({
3932
4048
  await stack.waitForDeployComplete();
3933
4049
 
3934
4050
  console.log(stack.skippedResources.map((resource) => resource.logicalId));
3935
- // ["AlarmRule"]
4051
+ // ["SearchApi"]
3936
4052
 
3937
- console.log(stack.getResource("AlarmRule")?.skippedReason);
3938
- // "Unsupported sim CloudFormation Resource service CloudWatch"
4053
+ console.log(stack.getResource("SearchApi")?.skippedReason);
4054
+ // "Unsupported sim CloudFormation Resource service AppSync"
3939
4055
  ```
3940
4056
 
3941
4057
  A skipped Resource is still there for `stack.getResource(...)`, and still answers `Ref` and `Fn::GetAtt` with
@@ -3979,8 +4095,11 @@ const stack = await simAws.cloudFormation().deployTemplate({
3979
4095
  Description: "/opt/awscli/aws",
3980
4096
  },
3981
4097
  },
3982
- AlarmRule: {
3983
- Type: "AWS::CloudWatch::Alarm",
4098
+ SearchApi: {
4099
+ Type: "AWS::AppSync::GraphQLApi",
4100
+ Properties: {
4101
+ Name: "search",
4102
+ },
3984
4103
  },
3985
4104
  },
3986
4105
  },
@@ -3989,7 +4108,7 @@ const stack = await simAws.cloudFormation().deployTemplate({
3989
4108
  await stack.waitForDeployComplete();
3990
4109
 
3991
4110
  console.log(stack.skippedResources.map((resource) => resource.logicalId));
3992
- // ["AlarmRule"]
4111
+ // ["SearchApi"]
3993
4112
 
3994
4113
  console.log(stack.inertResources.map((resource) => resource.logicalId));
3995
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