@kensio/yulin 1.20.14 → 1.20.16
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.
- package/README.md +13 -0
- package/{dist/config/oxlint/cffjs2.oxlintrc.json → cffjs2.oxlintrc.json} +1 -1
- package/dist/config/oxlint/cffjs2.oxlint.config.js +4 -3
- package/dist/sdk/module/sim-sdk-module-client-interceptor.d.ts +5 -0
- package/dist/sdk/module/sim-sdk-module-client-interceptor.js +14 -0
- package/dist/sdk/module/sim-sdk-static-client-factory.d.ts +26 -0
- package/dist/sdk/module/sim-sdk-static-client-factory.js +59 -0
- package/dist/sdk/send-patch.d.ts +8 -0
- package/dist/sdk/send-patch.js +11 -0
- package/dist/service/aws/caller/sim-aws-caller-resolver.d.ts +28 -6
- package/dist/service/aws/caller/sim-aws-caller-resolver.js +18 -11
- package/dist/service/aws/caller/sim-aws-caller.d.ts +9 -0
- package/dist/service/aws/caller/sim-aws-resolved-caller.d.ts +19 -0
- package/dist/service/aws/caller/sim-aws-resolved-caller.js +26 -0
- package/dist/service/aws/factory/sim-aws-account-region-service-builder.d.ts +6 -0
- package/dist/service/aws/factory/sim-aws-account-region-service-builder.js +3 -0
- package/dist/service/aws/factory/sim-aws-account-service-cache.d.ts +6 -0
- package/dist/service/aws/factory/sim-aws-account-service-cache.js +5 -0
- package/dist/service/aws/factory/sim-aws-service-factory.d.ts +5 -0
- package/dist/service/aws/factory/sim-aws-service-factory.js +6 -0
- package/dist/service/aws/sim-aws-properties.d.ts +15 -0
- package/dist/service/aws/sim-aws.js +2 -1
- package/dist/service/cloudformation/resource/resolve/property/sim-cfn-resource-property-resolver.d.ts +11 -4
- package/dist/service/cloudformation/resource/resolve/property/sim-cfn-resource-property-resolver.js +21 -8
- package/dist/service/cloudformation/resource/sim-cfn-resource.type.d.ts +9 -0
- package/dist/service/cloudformation/template/dynamic/make-sim-cfn-dynamic-references.d.ts +2 -0
- package/dist/service/cloudformation/template/dynamic/make-sim-cfn-dynamic-references.js +2 -2
- package/dist/service/cloudformation/template/dynamic/sim-cfn-dynamic-reference-resolvers.d.ts +10 -0
- package/dist/service/cloudformation/template/dynamic/sim-cfn-dynamic-reference-resolvers.js +4 -3
- package/dist/service/cloudformation/template/dynamic/sim-cfn-dynamic-reference-scan.d.ts +11 -0
- package/dist/service/cloudformation/template/dynamic/sim-cfn-dynamic-reference-scan.js +21 -0
- package/dist/service/cloudformation/template/dynamic/sim-cfn-dynamic-references.d.ts +24 -2
- package/dist/service/cloudformation/template/dynamic/sim-cfn-dynamic-references.js +50 -11
- package/dist/service/cloudformation/template/dynamic/sim-cfn-prefetched-dynamic-references.d.ts +42 -0
- package/dist/service/cloudformation/template/dynamic/sim-cfn-prefetched-dynamic-references.js +80 -0
- package/dist/service/eventbridge/delivery/sim-event-bridge-delivery-failures.d.ts +16 -0
- package/dist/service/eventbridge/delivery/sim-event-bridge-delivery-failures.js +19 -0
- package/dist/service/iam/authorize/context/sim-iam-auth-z-caller-context-builder.d.ts +30 -7
- package/dist/service/iam/authorize/context/sim-iam-auth-z-caller-context-builder.js +20 -11
- package/dist/service/iam/authorize/context/sim-iam-auth-z-context-builder.d.ts +2 -1
- package/dist/service/iam/authorize/context/sim-iam-auth-z-context-builder.js +5 -1
- package/dist/service/iam/authorize/match/sim-iam-policy-principal-matcher.d.ts +10 -0
- package/dist/service/iam/authorize/match/sim-iam-policy-principal-matcher.js +15 -3
- package/dist/service/iam/authorize/sim-iam-account-auth-z.d.ts +11 -4
- package/dist/service/iam/authorize/sim-iam-account-auth-z.js +5 -3
- package/dist/service/iam/authorize/sim-iam-authorizer.d.ts +2 -1
- package/dist/service/iam/authorize/sim-iam-authorizer.js +1 -0
- package/dist/service/iam/command/policy/create-policy/create-policy-input-resolver.js +4 -1
- package/dist/service/iam/command/policy/put-role-policy/put-role-policy.handler.js +5 -1
- package/dist/service/iam/command/policy/put-user-policy/put-user-policy.handler.js +5 -1
- package/dist/service/iam/policy/parse/sim-iam-document-parser.d.ts +4 -1
- package/dist/service/iam/policy/parse/sim-iam-document-parser.js +13 -15
- package/dist/service/iam/policy/sim-iam-statement-label.d.ts +28 -0
- package/dist/service/iam/policy/sim-iam-statement-label.js +17 -0
- package/dist/service/iam/policy/sim-iam-statement-strings.d.ts +11 -0
- package/dist/service/iam/policy/sim-iam-statement-strings.js +27 -0
- package/dist/service/iam/role/sim-iam-role-with-policy.factory.d.ts +9 -0
- package/dist/service/iam/role/sim-iam-role-with-policy.factory.js +4 -2
- package/dist/service/iam/sim-iam-account-parts.d.ts +8 -0
- package/dist/service/iam/sim-iam-account-parts.js +1 -0
- package/dist/service/iam/validate/sim-iam-policy-document-validator.d.ts +9 -2
- package/dist/service/iam/validate/sim-iam-policy-document-validator.js +37 -19
- package/dist/service/lambda/function/code/sim-lambda-vm-zip-function.factory.d.ts +7 -0
- package/dist/service/lambda/function/code/sim-lambda-vm-zip-function.factory.js +1 -0
- package/dist/service/lambda/function/code/vm/sdk/sim-lambda-provided-sdk-module.d.ts +19 -0
- package/dist/service/lambda/function/code/vm/sdk/sim-lambda-provided-sdk-module.js +78 -0
- package/dist/service/lambda/function/code/vm/sdk/sim-lambda-sdk-packages-not-installed.error.d.ts +15 -0
- package/dist/service/lambda/function/code/vm/sdk/sim-lambda-sdk-packages-not-installed.error.js +27 -0
- package/dist/service/lambda/function/code/vm/sdk/sim-lambda-vm-sdk-module-provider.d.ts +9 -0
- package/dist/service/lambda/function/code/vm/sdk/sim-sdk-lambda-vm-module-provider.d.ts +10 -0
- package/dist/service/lambda/function/code/vm/sdk/sim-sdk-lambda-vm-module-provider.js +28 -34
- package/dist/service/lambda/function/code/vm/sim-lambda-host-modules.d.ts +15 -0
- package/dist/service/lambda/function/code/vm/sim-lambda-host-modules.js +31 -0
- package/dist/service/lambda/function/code/vm/sim-lambda-vm-module-resolver.d.ts +7 -0
- package/dist/service/lambda/function/code/vm/sim-lambda-vm-module-resolver.js +15 -0
- package/dist/service/lambda/function/code/vm/sim-lambda-vm-modules.js +5 -4
- package/dist/service/s3/command/put-bucket-policy/put-bucket-policy.handler.js +4 -1
- package/dist/service/scheduler/delivery/sim-scheduler-delivery-failures.d.ts +16 -0
- package/dist/service/scheduler/delivery/sim-scheduler-delivery-failures.js +19 -0
- package/dist/service/scheduler/index.d.ts +1 -1
- package/dist/service/scheduler/index.js +1 -1
- package/dist/service/secretsmanager/cfn/dynamic/sim-cfn-secrets-manager-dynamic-reference-resolver.d.ts +13 -15
- package/dist/service/secretsmanager/cfn/dynamic/sim-cfn-secrets-manager-dynamic-reference-resolver.js +22 -43
- package/dist/service/secretsmanager/cfn/dynamic/sim-cfn-secrets-manager-reference-stand-in.d.ts +9 -0
- package/dist/service/secretsmanager/cfn/dynamic/sim-cfn-secrets-manager-reference-stand-in.js +14 -0
- package/dist/service/secretsmanager/cfn/dynamic/sim-cfn-secrets-manager-reference-value.d.ts +12 -0
- package/dist/service/secretsmanager/cfn/dynamic/sim-cfn-secrets-manager-reference-value.js +29 -0
- package/dist/service/ssm/cfn/dynamic/sim-cfn-ssm-dynamic-reference-resolver.d.ts +22 -6
- package/dist/service/ssm/cfn/dynamic/sim-cfn-ssm-dynamic-reference-resolver.js +39 -24
- package/dist/service/ssm/cfn/dynamic/sim-cfn-ssm-secure-dynamic-reference-resolver.d.ts +10 -5
- package/dist/service/ssm/cfn/dynamic/sim-cfn-ssm-secure-dynamic-reference-resolver.js +10 -8
- package/dist/service/ssm/sim-ssm.d.ts +10 -2
- package/dist/service/ssm/sim-ssm.js +12 -4
- package/dist/service/sts/auth-z/assume-role-auth-z-coordinator.d.ts +9 -2
- package/dist/service/sts/auth-z/assume-role-auth-z-coordinator.js +2 -2
- package/dist/service/sts/auth-z/assume-role-source-account-auth-z.d.ts +10 -4
- package/dist/service/sts/auth-z/assume-role-source-account-auth-z.js +8 -7
- package/dist/service/sts/auth-z/assume-role-target-auth-z.d.ts +2 -2
- package/dist/service/sts/auth-z/assume-role-trust-policy-authorizer.d.ts +6 -2
- package/dist/service/sts/auth-z/assume-role-trust-policy-authorizer.js +14 -11
- package/dist/service/sts/command/assume-role/assume-role.handler.d.ts +5 -1
- package/dist/service/sts/command/assume-role/assume-role.handler.js +5 -2
- package/dist/service/sts/command/get-caller-identity/get-caller-identity.handler.d.ts +5 -1
- package/dist/service/sts/command/get-caller-identity/get-caller-identity.handler.js +4 -1
- package/dist/service/sts/service-role/sim-service-role.js +5 -1
- package/dist/service/sts/sim-sts.d.ts +9 -1
- package/dist/service/sts/sim-sts.js +4 -0
- package/docs/README.md +54 -0
- package/docs/ai-skill/README.md +69 -0
- package/docs/cli/README.md +284 -0
- package/docs/factories/README.md +140 -0
- package/docs/lint/README.md +176 -0
- package/docs/non-aws-dependencies/README.md +261 -0
- package/docs/sdk/README.md +272 -0
- package/docs/serve/README.md +1126 -0
- package/docs/services/acm/README.md +823 -0
- package/docs/services/apigateway/README.md +1919 -0
- package/docs/services/apigatewayv2/README.md +2678 -0
- package/docs/services/athena/README.md +1044 -0
- package/docs/services/bedrock/README.md +350 -0
- package/docs/services/cloudformation/README.md +3440 -0
- package/docs/services/cloudfront/README.md +2874 -0
- package/docs/services/cloudwatch/README.md +420 -0
- package/docs/services/cognito/README.md +4923 -0
- package/docs/services/dynamodb/README.md +3784 -0
- package/docs/services/ecr/README.md +302 -0
- package/docs/services/ecs/README.md +2010 -0
- package/docs/services/elbv2/README.md +1897 -0
- package/docs/services/eventbridge/README.md +1110 -0
- package/docs/services/firehose/README.md +805 -0
- package/docs/services/glue/README.md +501 -0
- package/docs/services/iam/README.md +1283 -0
- package/docs/services/kinesis/README.md +454 -0
- package/docs/services/kms/README.md +659 -0
- package/docs/services/lambda/README.md +3645 -0
- package/docs/services/logs/README.md +868 -0
- package/docs/services/organizations/README.md +574 -0
- package/docs/services/personalize/README.md +835 -0
- package/docs/services/rekognition/README.md +1070 -0
- package/docs/services/route53/README.md +1783 -0
- package/docs/services/s3/README.md +3024 -0
- package/docs/services/scheduler/README.md +806 -0
- package/docs/services/secretsmanager/README.md +667 -0
- package/docs/services/ses/README.md +1011 -0
- package/docs/services/sns/README.md +1904 -0
- package/docs/services/sqs/README.md +1185 -0
- package/docs/services/ssm/README.md +1072 -0
- package/docs/services/stepfunctions/README.md +1332 -0
- package/docs/services/sts/README.md +245 -0
- package/docs/services/wafv2/README.md +1378 -0
- package/docs/terraform/README.md +248 -0
- package/docs/time/README.md +284 -0
- package/llms.txt +59 -0
- package/package.json +9 -5
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
# Simulated Kinesis Data Streams
|
|
2
|
+
|
|
3
|
+
Yulin includes a simulated Kinesis Data Streams for tests and local development. It creates streams,
|
|
4
|
+
places records on shards the way real Kinesis does, and hands them back through shard iterators. A
|
|
5
|
+
test can put an event and assert that the consumer read it, without an AWS account and without
|
|
6
|
+
waiting on a real stream.
|
|
7
|
+
|
|
8
|
+
Kinesis specific types are imported from the `@kensio/yulin/kinesis` subpath.
|
|
9
|
+
|
|
10
|
+
## Putting a record and reading it back
|
|
11
|
+
|
|
12
|
+
`simAws.kinesis()` gives the service for the default account and region. Records go on with
|
|
13
|
+
`PutRecord`, and come off through a shard iterator, which is the walk every Kinesis consumer makes.
|
|
14
|
+
|
|
15
|
+
```typescript sim-kinesis-put-and-read
|
|
16
|
+
/**
|
|
17
|
+
* Putting an order event on a stream and reading it back.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
CreateStreamCommand,
|
|
22
|
+
GetRecordsCommand,
|
|
23
|
+
GetShardIteratorCommand,
|
|
24
|
+
PutRecordCommand,
|
|
25
|
+
} from "@aws-sdk/client-kinesis";
|
|
26
|
+
|
|
27
|
+
import { SimAws } from "@kensio/yulin";
|
|
28
|
+
|
|
29
|
+
const simAws = new SimAws();
|
|
30
|
+
const kinesis = simAws.kinesis();
|
|
31
|
+
|
|
32
|
+
await kinesis.createStream(
|
|
33
|
+
new CreateStreamCommand({ StreamName: "orders", ShardCount: 1 }),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const orderEvent = new TextEncoder().encode(JSON.stringify({ id: "order-1" }));
|
|
37
|
+
|
|
38
|
+
await kinesis.putRecord(
|
|
39
|
+
new PutRecordCommand({
|
|
40
|
+
StreamName: "orders",
|
|
41
|
+
PartitionKey: "customer-1",
|
|
42
|
+
Data: orderEvent,
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const { ShardIterator } = await kinesis.getShardIterator(
|
|
47
|
+
new GetShardIteratorCommand({
|
|
48
|
+
StreamName: "orders",
|
|
49
|
+
ShardId: "shardId-000000000000",
|
|
50
|
+
ShardIteratorType: "TRIM_HORIZON",
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const { Records } = await kinesis.getRecords(
|
|
55
|
+
new GetRecordsCommand({ ShardIterator }),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// {"id":"order-1"}
|
|
59
|
+
console.log(new TextDecoder().decode(Records[0]?.Data));
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
A record keeps the bytes it was given. Whatever the producer encoded is what the consumer decodes.
|
|
63
|
+
|
|
64
|
+
## Shards and partition keys
|
|
65
|
+
|
|
66
|
+
A stream is created with the shard count it asks for, and each shard owns a slice of a 128 bit hash
|
|
67
|
+
key space. A record goes to the shard whose slice covers the MD5 hash of its partition key, which is
|
|
68
|
+
the placement real Kinesis makes. Two records sharing a partition key therefore land on one shard,
|
|
69
|
+
in the order they were put, and that per-key ordering is what most Kinesis consumers depend on.
|
|
70
|
+
|
|
71
|
+
Records under different partition keys can land anywhere. A consumer that has to see every record
|
|
72
|
+
reads every shard.
|
|
73
|
+
|
|
74
|
+
```typescript sim-kinesis-shards
|
|
75
|
+
/**
|
|
76
|
+
* Reading which shard a record landed on, and which slice each shard owns.
|
|
77
|
+
*/
|
|
78
|
+
|
|
79
|
+
import {
|
|
80
|
+
CreateStreamCommand,
|
|
81
|
+
DescribeStreamCommand,
|
|
82
|
+
PutRecordCommand,
|
|
83
|
+
} from "@aws-sdk/client-kinesis";
|
|
84
|
+
|
|
85
|
+
import { SimAws } from "@kensio/yulin";
|
|
86
|
+
|
|
87
|
+
const simAws = new SimAws();
|
|
88
|
+
const kinesis = simAws.kinesis();
|
|
89
|
+
|
|
90
|
+
await kinesis.createStream(
|
|
91
|
+
new CreateStreamCommand({ StreamName: "orders", ShardCount: 2 }),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const first = await kinesis.putRecord(
|
|
95
|
+
new PutRecordCommand({
|
|
96
|
+
StreamName: "orders",
|
|
97
|
+
PartitionKey: "customer-1",
|
|
98
|
+
Data: new TextEncoder().encode("order-1"),
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const second = await kinesis.putRecord(
|
|
103
|
+
new PutRecordCommand({
|
|
104
|
+
StreamName: "orders",
|
|
105
|
+
PartitionKey: "customer-1",
|
|
106
|
+
Data: new TextEncoder().encode("order-2"),
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// true: one partition key means one shard.
|
|
111
|
+
console.log(first.ShardId === second.ShardId);
|
|
112
|
+
|
|
113
|
+
const { StreamDescription } = await kinesis.describeStream(
|
|
114
|
+
new DescribeStreamCommand({ StreamName: "orders" }),
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// shardId-000000000000 0
|
|
118
|
+
console.log(
|
|
119
|
+
StreamDescription.Shards[0]?.ShardId,
|
|
120
|
+
StreamDescription.Shards[0]?.HashKeyRange.StartingHashKey,
|
|
121
|
+
);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
An `ExplicitHashKey` on a record overrides the partition key for placement, and the record still
|
|
125
|
+
carries the partition key the producer gave it. That is how a producer pins a record to a shard it
|
|
126
|
+
picked.
|
|
127
|
+
|
|
128
|
+
A stream created with `StreamModeDetails` of `ON_DEMAND` gets four shards, which is what real
|
|
129
|
+
Kinesis starts an on-demand stream with. Nothing here grows or shrinks that count.
|
|
130
|
+
|
|
131
|
+
## Where a read starts
|
|
132
|
+
|
|
133
|
+
Every shard iterator type resolves to a place on the shard.
|
|
134
|
+
|
|
135
|
+
| `ShardIteratorType` | Starts at |
|
|
136
|
+
| ----------------------- | ------------------------------------------------------------- |
|
|
137
|
+
| `TRIM_HORIZON` | The oldest record the shard still holds. |
|
|
138
|
+
| `LATEST` | After the newest record at the moment the iterator was taken. |
|
|
139
|
+
| `AT_SEQUENCE_NUMBER` | The record with that sequence number. |
|
|
140
|
+
| `AFTER_SEQUENCE_NUMBER` | The record following that sequence number. |
|
|
141
|
+
| `AT_TIMESTAMP` | The first record that arrived at or after the instant given. |
|
|
142
|
+
|
|
143
|
+
`GetRecords` hands back a `NextShardIterator` pointing at where the read finished, which is what a
|
|
144
|
+
polling consumer passes to its next call. A read that has caught up comes back empty with an
|
|
145
|
+
iterator standing where it was.
|
|
146
|
+
|
|
147
|
+
`MillisBehindLatest` reports how far behind the tip the reader is. Zero means caught up. Otherwise
|
|
148
|
+
it is the age of the last record handed back, measured against simulated time.
|
|
149
|
+
|
|
150
|
+
## Retention
|
|
151
|
+
|
|
152
|
+
A stream keeps a record for 24 hours. Records older than that are gone from a read, and trimming is
|
|
153
|
+
applied at the instant of the read rather than on a timer, so moving simulated time forward is all a
|
|
154
|
+
test needs.
|
|
155
|
+
|
|
156
|
+
`IncreaseStreamRetentionPeriod` and `DecreaseStreamRetentionPeriod` move it, up to the 8760 hours
|
|
157
|
+
Kinesis keeps at most. Each refuses a request that goes the other way, including one asking for what
|
|
158
|
+
the stream already keeps, which is what real Kinesis does with a caller that has the wrong idea of
|
|
159
|
+
what the stream is set to. Shortening the window drops whatever it has already outlived from the
|
|
160
|
+
next read.
|
|
161
|
+
|
|
162
|
+
```typescript sim-kinesis-retention
|
|
163
|
+
/**
|
|
164
|
+
* Ageing a record out of a stream's retention window.
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
import {
|
|
168
|
+
CreateStreamCommand,
|
|
169
|
+
GetRecordsCommand,
|
|
170
|
+
GetShardIteratorCommand,
|
|
171
|
+
PutRecordCommand,
|
|
172
|
+
} from "@aws-sdk/client-kinesis";
|
|
173
|
+
|
|
174
|
+
import { SimAws, SimFixedClock } from "@kensio/yulin";
|
|
175
|
+
|
|
176
|
+
const simAws = new SimAws({
|
|
177
|
+
clock: new SimFixedClock(new Date("2026-08-22T09:00:00.000Z")),
|
|
178
|
+
});
|
|
179
|
+
const kinesis = simAws.kinesis();
|
|
180
|
+
|
|
181
|
+
await kinesis.createStream(new CreateStreamCommand({ StreamName: "orders" }));
|
|
182
|
+
|
|
183
|
+
await kinesis.putRecord(
|
|
184
|
+
new PutRecordCommand({
|
|
185
|
+
StreamName: "orders",
|
|
186
|
+
PartitionKey: "customer-1",
|
|
187
|
+
Data: new TextEncoder().encode("order-1"),
|
|
188
|
+
}),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
await simAws.clock().advanceBy({ hours: 25 });
|
|
192
|
+
|
|
193
|
+
const { ShardIterator } = await kinesis.getShardIterator(
|
|
194
|
+
new GetShardIteratorCommand({
|
|
195
|
+
StreamName: "orders",
|
|
196
|
+
ShardId: "shardId-000000000000",
|
|
197
|
+
ShardIteratorType: "TRIM_HORIZON",
|
|
198
|
+
}),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const { Records } = await kinesis.getRecords(
|
|
202
|
+
new GetRecordsCommand({ ShardIterator }),
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// 0: the record aged out of the 24 hour window.
|
|
206
|
+
console.log(Records.length);
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Triggering a Lambda function
|
|
210
|
+
|
|
211
|
+
A [Lambda event source mapping](https://yulinsim.dev/services/lambda/#triggering-a-function-from-a-kinesis-stream "Simulated Lambda Kinesis event source docs")
|
|
212
|
+
polls a stream and invokes a function with the records it reads. Every shard is read by a processor
|
|
213
|
+
of its own, as real Lambda reads one, and the function's execution role is what the polling is done
|
|
214
|
+
as.
|
|
215
|
+
|
|
216
|
+
## Feeding a Firehose delivery stream
|
|
217
|
+
|
|
218
|
+
A [Firehose delivery stream](https://yulinsim.dev/services/firehose/#reading-from-a-kinesis-stream "Simulated Firehose Kinesis source docs")
|
|
219
|
+
can read a stream and buffer what it reads into an S3 Bucket. It reads every shard as its source
|
|
220
|
+
`RoleARN`, starting at the end of the stream when the delivery stream is created.
|
|
221
|
+
|
|
222
|
+
## Deploying a stream
|
|
223
|
+
|
|
224
|
+
`AWS::Kinesis::Stream` creates a simulated stream, which is what a CDK `Stream` synthesizes. The
|
|
225
|
+
stream goes through the ordinary `CreateStream` command, so a stream a template deployed is the same
|
|
226
|
+
thing an SDK caller would have got, and a template asking for something Kinesis will not take is
|
|
227
|
+
refused in the words `CreateStream` refuses it in.
|
|
228
|
+
|
|
229
|
+
`Ref` gives the stream name and `Fn::GetAtt` on `Arn` gives the stream ARN, which is the way round
|
|
230
|
+
real CloudFormation publishes them. Every Kinesis API and every grant names the ARN, so a template
|
|
231
|
+
wiring a stream into a Lambda event source mapping or an IAM policy reads the attribute.
|
|
232
|
+
|
|
233
|
+
```typescript sim-kinesis-cloudformation
|
|
234
|
+
/**
|
|
235
|
+
* Deploying a Kinesis stream and putting a record onto it.
|
|
236
|
+
*/
|
|
237
|
+
|
|
238
|
+
import { PutRecordCommand } from "@aws-sdk/client-kinesis";
|
|
239
|
+
|
|
240
|
+
import { SimAws } from "@kensio/yulin";
|
|
241
|
+
|
|
242
|
+
const simAws = new SimAws();
|
|
243
|
+
|
|
244
|
+
const stack = await simAws.cloudFormation().deployTemplate({
|
|
245
|
+
stackName: "orders-stack",
|
|
246
|
+
template: {
|
|
247
|
+
Resources: {
|
|
248
|
+
OrdersStream: {
|
|
249
|
+
Type: "AWS::Kinesis::Stream",
|
|
250
|
+
Properties: {
|
|
251
|
+
Name: "orders",
|
|
252
|
+
ShardCount: 2,
|
|
253
|
+
RetentionPeriodHours: 168,
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
Outputs: {
|
|
258
|
+
StreamArn: { Value: { "Fn::GetAtt": ["OrdersStream", "Arn"] } },
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
await stack.waitForDeployComplete();
|
|
264
|
+
|
|
265
|
+
// arn:aws:kinesis:us-east-1:<account>:stream/orders
|
|
266
|
+
console.log(stack.outputs.get("StreamArn")?.value);
|
|
267
|
+
|
|
268
|
+
await simAws.kinesis().putRecord(
|
|
269
|
+
new PutRecordCommand({
|
|
270
|
+
StreamName: "orders",
|
|
271
|
+
PartitionKey: "customer-1",
|
|
272
|
+
Data: new TextEncoder().encode("order-1"),
|
|
273
|
+
}),
|
|
274
|
+
);
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
`Name`, `ShardCount`, `RetentionPeriodHours`, `StreamModeDetails` and `Tags` are read. A stream the
|
|
278
|
+
template does not name is named after the stack and the logical ID, as real CloudFormation names
|
|
279
|
+
one.
|
|
280
|
+
|
|
281
|
+
`RetentionPeriodHours` is applied after the stream is created, because `CreateStream` takes no
|
|
282
|
+
retention on real Kinesis either. It only ever goes up: a new stream keeps records for 24 hours,
|
|
283
|
+
which is also the least Kinesis accepts, so a template can ask for more or for the same and never
|
|
284
|
+
for less.
|
|
285
|
+
|
|
286
|
+
`StreamEncryption` and `DesiredShardLevelMetrics` are recorded against the resource as unsimulated
|
|
287
|
+
and the stream is created anyway, so a template that encrypts its streams still deploys and the
|
|
288
|
+
omission is somewhere a test can find it. Deleting the stack deletes the stream.
|
|
289
|
+
|
|
290
|
+
`AWS::Kinesis::StreamConsumer` and `AWS::Kinesis::ResourcePolicy` are reported as unsupported and
|
|
291
|
+
skipped. One registers an enhanced fan-out consumer and the other admits a caller from another
|
|
292
|
+
account, and neither has anything to act on here.
|
|
293
|
+
|
|
294
|
+
## Permissions
|
|
295
|
+
|
|
296
|
+
Every operation goes through simulated IAM. The action is the `kinesis:` name of the operation, and
|
|
297
|
+
the resource is the stream ARN, `arn:aws:kinesis:<region>:<account>:stream/<name>`. `ListStreams`
|
|
298
|
+
names no stream and authorizes against `*`.
|
|
299
|
+
|
|
300
|
+
`GetRecords` authorizes against the stream the iterator was made on, which the iterator carries. A
|
|
301
|
+
caller cannot reach a stream it lacks permission for by holding someone else's iterator.
|
|
302
|
+
|
|
303
|
+
```typescript sim-kinesis-permissions
|
|
304
|
+
/**
|
|
305
|
+
* Refusing a producer that has no permission on the stream.
|
|
306
|
+
*/
|
|
307
|
+
|
|
308
|
+
import { PutRecordCommand } from "@aws-sdk/client-kinesis";
|
|
309
|
+
|
|
310
|
+
import { SimAws } from "@kensio/yulin";
|
|
311
|
+
|
|
312
|
+
const simAws = new SimAws();
|
|
313
|
+
|
|
314
|
+
// A Role allowed to read the stream and nothing else.
|
|
315
|
+
const { Role } = await simAws.iam().createRole({
|
|
316
|
+
input: {
|
|
317
|
+
RoleName: "OrderReader",
|
|
318
|
+
AssumeRolePolicyDocument: JSON.stringify({
|
|
319
|
+
Version: "2012-10-17",
|
|
320
|
+
Statement: [
|
|
321
|
+
{
|
|
322
|
+
Effect: "Allow",
|
|
323
|
+
Principal: { AWS: `arn:aws:iam::${simAws.defaultAccountId}:root` },
|
|
324
|
+
Action: "sts:AssumeRole",
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
}),
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
await simAws.iam().putRolePolicy({
|
|
332
|
+
input: {
|
|
333
|
+
RoleName: "OrderReader",
|
|
334
|
+
PolicyName: "ReadOrders",
|
|
335
|
+
PolicyDocument: JSON.stringify({
|
|
336
|
+
Version: "2012-10-17",
|
|
337
|
+
Statement: [
|
|
338
|
+
{ Effect: "Allow", Action: "kinesis:GetRecords", Resource: "*" },
|
|
339
|
+
],
|
|
340
|
+
}),
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
await simAws.kinesis().createStream({ input: { StreamName: "orders" } });
|
|
345
|
+
|
|
346
|
+
try {
|
|
347
|
+
await simAws.kinesis().putRecord(
|
|
348
|
+
new PutRecordCommand({
|
|
349
|
+
StreamName: "orders",
|
|
350
|
+
PartitionKey: "customer-1",
|
|
351
|
+
Data: new TextEncoder().encode("order-1"),
|
|
352
|
+
}),
|
|
353
|
+
{ caller: { kind: "arn", arn: Role.Arn } },
|
|
354
|
+
);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
// User: arn:aws:iam::...:role/OrderReader is not authorized to perform:
|
|
357
|
+
// kinesis:PutRecord on resource: arn:aws:kinesis:...:stream/orders
|
|
358
|
+
console.log((error as Error).message);
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## SDK interception
|
|
363
|
+
|
|
364
|
+
A `KinesisClient` handed to `SimSdk` reaches the simulated service, so application code that builds
|
|
365
|
+
its own client needs no change.
|
|
366
|
+
|
|
367
|
+
```typescript sim-kinesis-sdk-interception
|
|
368
|
+
/**
|
|
369
|
+
* Running unchanged Kinesis application code against the simulator.
|
|
370
|
+
*/
|
|
371
|
+
|
|
372
|
+
import {
|
|
373
|
+
CreateStreamCommand,
|
|
374
|
+
KinesisClient,
|
|
375
|
+
PutRecordCommand,
|
|
376
|
+
} from "@aws-sdk/client-kinesis";
|
|
377
|
+
|
|
378
|
+
import { SimSdk } from "@kensio/yulin/sdk";
|
|
379
|
+
|
|
380
|
+
using simSdk = new SimSdk();
|
|
381
|
+
simSdk.intercept(KinesisClient);
|
|
382
|
+
|
|
383
|
+
// Ordinary application code, with nothing about it that knows it is simulated.
|
|
384
|
+
const kinesis = new KinesisClient({ region: "eu-west-2" });
|
|
385
|
+
|
|
386
|
+
await kinesis.send(
|
|
387
|
+
new CreateStreamCommand({ StreamName: "orders", ShardCount: 1 }),
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
const put = await kinesis.send(
|
|
391
|
+
new PutRecordCommand({
|
|
392
|
+
StreamName: "orders",
|
|
393
|
+
PartitionKey: "customer-1",
|
|
394
|
+
Data: new TextEncoder().encode("order-1"),
|
|
395
|
+
}),
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
// shardId-000000000000
|
|
399
|
+
console.log(put.ShardId);
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## Supported commands
|
|
403
|
+
|
|
404
|
+
| Command | Notes |
|
|
405
|
+
| ------------------------------- | ---------------------------------------------------------------------- |
|
|
406
|
+
| `CreateStream` | A name already in use raises `ResourceInUseException`. |
|
|
407
|
+
| `DeleteStream` | The name is free again at once. `EnforceConsumerDeletion` is accepted. |
|
|
408
|
+
| `ListStreams` | Sorted by name, paged with `Limit` and `NextToken`. |
|
|
409
|
+
| `DescribeStream` | Shards paged with `Limit` and `ExclusiveStartShardId`. |
|
|
410
|
+
| `DescribeStreamSummary` | Reports the open shard count instead of the shards. |
|
|
411
|
+
| `IncreaseStreamRetentionPeriod` | Refused unless it asks for more than the stream keeps now. |
|
|
412
|
+
| `DecreaseStreamRetentionPeriod` | Refused unless it asks for less than the stream keeps now. |
|
|
413
|
+
| `PutRecord` | `SequenceNumberForOrdering` is accepted and already guaranteed. |
|
|
414
|
+
| `PutRecords` | Up to 500 records and 5 MB. `FailedRecordCount` is always zero. |
|
|
415
|
+
| `GetShardIterator` | Every iterator type resolves. |
|
|
416
|
+
| `GetRecords` | `Limit` up to 10,000. Reports `MillisBehindLatest`. |
|
|
417
|
+
|
|
418
|
+
Every operation takes `StreamName` or `StreamARN`, and reads the ARN when a request carries both.
|
|
419
|
+
|
|
420
|
+
Anything else refuses on send with `SimSdkUnsupportedCommandError`.
|
|
421
|
+
|
|
422
|
+
## Divergences and limitations
|
|
423
|
+
|
|
424
|
+
- **A stream is `ACTIVE` as soon as it exists.** Real Kinesis reports `CREATING` while it brings the
|
|
425
|
+
shards up, and a status a test has to poll through earns nothing when there are no shards to bring
|
|
426
|
+
up. `DELETING` and `UPDATING` are absent for the same reason.
|
|
427
|
+
- **Nothing reshards.** `UpdateShardCount`, `SplitShard` and `MergeShards` move the shard map
|
|
428
|
+
underneath consumers holding iterators, and they are left out. A shard is opened when the stream
|
|
429
|
+
is created and never closes, so no shard reports an ending sequence number and no read reports a
|
|
430
|
+
child shard.
|
|
431
|
+
- **Enhanced fan-out is absent.** `RegisterStreamConsumer`, `DeregisterStreamConsumer`,
|
|
432
|
+
`ListStreamConsumers`, `DescribeStreamConsumer` and `SubscribeToShard` need an HTTP/2 event stream
|
|
433
|
+
that nothing here delivers. Every consumer reads through `GetRecords`.
|
|
434
|
+
- **A shard iterator never expires.** Real Kinesis expires one after five minutes. An iterator this
|
|
435
|
+
simulation never issued is still refused, with the `ExpiredIteratorException` real Kinesis uses.
|
|
436
|
+
- **Throughput is unlimited.** Real Kinesis takes 1 MB or 1,000 records a second per shard for
|
|
437
|
+
writes and 2 MB a second for reads, and refuses past that with
|
|
438
|
+
`ProvisionedThroughputExceededException`. Nothing here counts. That is why `FailedRecordCount` on
|
|
439
|
+
`PutRecords` is always zero: the reasons real Kinesis fails one record of a batch are throughput
|
|
440
|
+
limits and internal faults, and neither is simulated. The per-record result shape is still what a
|
|
441
|
+
consumer of the response reads.
|
|
442
|
+
- **Sequence numbers are 56 digit counters.** They are unique within a stream and increase within a
|
|
443
|
+
shard, as real Kinesis promises. They also increase across shards here, which real Kinesis does
|
|
444
|
+
not promise, so a consumer ordering two records from different shards would be relying on
|
|
445
|
+
something AWS does not offer.
|
|
446
|
+
- **Server-side encryption is absent.** `StartStreamEncryption` and `StopStreamEncryption` are left
|
|
447
|
+
out, and no response carries an `EncryptionType`.
|
|
448
|
+
- **Tags are kept and never listed.** A stream created with `Tags` holds them, readable through
|
|
449
|
+
`findStream`. `AddTagsToStream`, `ListTagsForStream` and `RemoveTagsFromStream` are absent.
|
|
450
|
+
- **Kinesis Data Firehose is a separate service.** It has a simulation of its own under
|
|
451
|
+
`simAws.firehose()`, and a delivery stream there can read a stream here. Kinesis Video Streams is
|
|
452
|
+
absent.
|
|
453
|
+
- **`AWS::DynamoDB::Table` `KinesisStreamSpecification` stays unsimulated.** A table does not publish
|
|
454
|
+
its changes into a stream here.
|