@kensio/yulin 1.20.15 → 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/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/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/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/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 +7 -4
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
# Simulated Glue
|
|
2
|
+
|
|
3
|
+
Yulin includes a simulated Glue Data Catalog for tests and local development. It holds databases,
|
|
4
|
+
tables and the partitions registered against them, deploys databases and tables from
|
|
5
|
+
`AWS::Glue::Database` and `AWS::Glue::Table`, and hands them back through `GetDatabase` and
|
|
6
|
+
`GetTable`. A test can assert that a stack declared the table definition it meant to, including the
|
|
7
|
+
Athena partition projection its parameters configure.
|
|
8
|
+
|
|
9
|
+
A table here is a definition. The data it describes stays in S3, unread, and the catalog answers
|
|
10
|
+
with what it was told to hold.
|
|
11
|
+
|
|
12
|
+
Simulated [Athena](https://yulinsim.dev/services/athena/ "Simulated Athena usage docs") reads this
|
|
13
|
+
catalog. A query naming a table no database here holds fails the way real Athena fails it, and a
|
|
14
|
+
table's partition projection is evaluated when a query runs against it. All four projection types
|
|
15
|
+
are covered, `enum`, `integer`, `date` and `injected`. A projection with a mistake in its parameters
|
|
16
|
+
fails the query that reads it.
|
|
17
|
+
|
|
18
|
+
Glue-specific types are imported from the `@kensio/yulin/glue` subpath.
|
|
19
|
+
|
|
20
|
+
## Deploying a database and a table
|
|
21
|
+
|
|
22
|
+
A stack declares the database, and the table names it through `Ref`. Both read back through the SDK
|
|
23
|
+
once the deploy finishes.
|
|
24
|
+
|
|
25
|
+
```typescript sim-glue-cloudformation
|
|
26
|
+
/**
|
|
27
|
+
* Deploying a Glue database and a table, then reading the table back.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { GetTableCommand } from "@aws-sdk/client-glue";
|
|
31
|
+
|
|
32
|
+
import { SimAws } from "@kensio/yulin";
|
|
33
|
+
|
|
34
|
+
const simAws = new SimAws();
|
|
35
|
+
|
|
36
|
+
const stack = await simAws.cloudFormation().deployTemplate({
|
|
37
|
+
stackName: "analytics-stack",
|
|
38
|
+
template: {
|
|
39
|
+
Resources: {
|
|
40
|
+
LogDatabase: {
|
|
41
|
+
Type: "AWS::Glue::Database",
|
|
42
|
+
Properties: {
|
|
43
|
+
CatalogId: { Ref: "AWS::AccountId" },
|
|
44
|
+
DatabaseInput: { Name: "site_logs" },
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
LogTable: {
|
|
48
|
+
Type: "AWS::Glue::Table",
|
|
49
|
+
Properties: {
|
|
50
|
+
CatalogId: { Ref: "AWS::AccountId" },
|
|
51
|
+
DatabaseName: { Ref: "LogDatabase" },
|
|
52
|
+
TableInput: {
|
|
53
|
+
Name: "access_logs",
|
|
54
|
+
TableType: "EXTERNAL_TABLE",
|
|
55
|
+
PartitionKeys: [{ Name: "year", Type: "string" }],
|
|
56
|
+
StorageDescriptor: {
|
|
57
|
+
Columns: [{ Name: "status", Type: "int" }],
|
|
58
|
+
Location: "s3://site-logs/cloudfront/",
|
|
59
|
+
},
|
|
60
|
+
Parameters: {
|
|
61
|
+
"projection.enabled": "true",
|
|
62
|
+
"projection.year.type": "date",
|
|
63
|
+
"projection.year.format": "yyyy",
|
|
64
|
+
"projection.year.range": "2026,NOW",
|
|
65
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
66
|
+
"storage.location.template": "s3://site-logs/cloudfront/${year}/",
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
await stack.waitForDeployComplete();
|
|
76
|
+
|
|
77
|
+
const { Table } = simAws
|
|
78
|
+
.glue()
|
|
79
|
+
.getTable(
|
|
80
|
+
new GetTableCommand({ DatabaseName: "site_logs", Name: "access_logs" }),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// true
|
|
84
|
+
console.log(Table.Parameters["projection.enabled"]);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Partition projection lives entirely in `TableInput.Parameters`. Those parameters are read into the
|
|
88
|
+
table, and never recorded as ignored. A table whose parameters were dropped on the way in deploys
|
|
89
|
+
green while projecting none of them. Simulated Athena reads those same parameters when a query runs.
|
|
90
|
+
A broken projection fails that query.
|
|
91
|
+
|
|
92
|
+
`Ref` answers with the database name and with the table name.
|
|
93
|
+
|
|
94
|
+
## Names are folded to lower case
|
|
95
|
+
|
|
96
|
+
A database name and a table name are both folded to lower case when they are stored. Real Glue folds
|
|
97
|
+
them the same way, for compatibility with Apache Hive. A database created as `Rainlytics` is stored,
|
|
98
|
+
reported and queried as `rainlytics`.
|
|
99
|
+
|
|
100
|
+
```typescript sim-glue-name-folding
|
|
101
|
+
/**
|
|
102
|
+
* A catalog name folded on its way in.
|
|
103
|
+
*/
|
|
104
|
+
|
|
105
|
+
import {
|
|
106
|
+
CreateDatabaseCommand,
|
|
107
|
+
GetDatabaseCommand,
|
|
108
|
+
} from "@aws-sdk/client-glue";
|
|
109
|
+
|
|
110
|
+
import { SimAws } from "@kensio/yulin";
|
|
111
|
+
|
|
112
|
+
const glue = new SimAws().glue();
|
|
113
|
+
|
|
114
|
+
glue.createDatabase(
|
|
115
|
+
new CreateDatabaseCommand({ DatabaseInput: { Name: "Rainlytics" } }),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const { Database } = glue.getDatabase(
|
|
119
|
+
new GetDatabaseCommand({ Name: "Rainlytics" }),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// rainlytics
|
|
123
|
+
console.log(Database.Name);
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Two names differing only by case are one name. A second `CreateDatabase` for `Rainlytics` after one
|
|
127
|
+
for `rainlytics` is an `AlreadyExistsException`, and a `GetTable` finds the table under whichever
|
|
128
|
+
spelling it is asked for.
|
|
129
|
+
|
|
130
|
+
Column names keep the case they were given, including partition keys. Real Glue leaves those alone,
|
|
131
|
+
and simulated [Athena](https://yulinsim.dev/services/athena/ "Simulated Athena usage docs") folds a
|
|
132
|
+
column name only when it runs a query.
|
|
133
|
+
|
|
134
|
+
## Reading the catalog back
|
|
135
|
+
|
|
136
|
+
`GetDatabase`, `GetDatabases`, `GetTable` and `GetTables` answer through the SDK. A `SimGlue` also
|
|
137
|
+
carries `findDatabase`, `findTable`, `allDatabases`, `tablesInDatabase`, `findPartition` and
|
|
138
|
+
`partitionsInTable`. Those read the same state without going through a Command or its
|
|
139
|
+
authorization.
|
|
140
|
+
|
|
141
|
+
The storage descriptor keeps its columns in the order they were declared, and the partition keys
|
|
142
|
+
keep theirs. The two stay apart, the way real Glue keeps them. A partition key repeated among the
|
|
143
|
+
storage descriptor's columns gives a table Athena refuses to query.
|
|
144
|
+
|
|
145
|
+
## Registering partitions
|
|
146
|
+
|
|
147
|
+
A crawler, `MSCK REPAIR TABLE` or `ALTER TABLE ADD PARTITION` fills a real catalog's partition list.
|
|
148
|
+
Here the six partition commands do it. A partition is keyed by its values in the order the table's
|
|
149
|
+
`PartitionKeys` declares them, and carries a storage descriptor saying where its own data sits.
|
|
150
|
+
|
|
151
|
+
```typescript sim-glue-partitions
|
|
152
|
+
/**
|
|
153
|
+
* Registering two days of partitions against a table, then listing them.
|
|
154
|
+
*/
|
|
155
|
+
|
|
156
|
+
import {
|
|
157
|
+
BatchCreatePartitionCommand,
|
|
158
|
+
CreateDatabaseCommand,
|
|
159
|
+
CreateTableCommand,
|
|
160
|
+
GetPartitionsCommand,
|
|
161
|
+
} from "@aws-sdk/client-glue";
|
|
162
|
+
|
|
163
|
+
import { SimAws } from "@kensio/yulin";
|
|
164
|
+
|
|
165
|
+
const simAws = new SimAws();
|
|
166
|
+
const glue = simAws.glue();
|
|
167
|
+
|
|
168
|
+
glue.createDatabase(
|
|
169
|
+
new CreateDatabaseCommand({ DatabaseInput: { Name: "site_logs" } }),
|
|
170
|
+
);
|
|
171
|
+
glue.createTable(
|
|
172
|
+
new CreateTableCommand({
|
|
173
|
+
DatabaseName: "site_logs",
|
|
174
|
+
TableInput: {
|
|
175
|
+
Name: "access_logs",
|
|
176
|
+
PartitionKeys: [{ Name: "day", Type: "string" }],
|
|
177
|
+
StorageDescriptor: { Location: "s3://site-logs/cloudfront/" },
|
|
178
|
+
},
|
|
179
|
+
}),
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const { Errors } = glue.batchCreatePartition(
|
|
183
|
+
new BatchCreatePartitionCommand({
|
|
184
|
+
DatabaseName: "site_logs",
|
|
185
|
+
TableName: "access_logs",
|
|
186
|
+
PartitionInputList: [
|
|
187
|
+
{
|
|
188
|
+
Values: ["2026-08-25"],
|
|
189
|
+
StorageDescriptor: {
|
|
190
|
+
Location: "s3://site-logs/cloudfront/day=2026-08-25/",
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
Values: ["2026-08-26"],
|
|
195
|
+
StorageDescriptor: {
|
|
196
|
+
Location: "s3://site-logs/cloudfront/day=2026-08-26/",
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
}),
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const { Partitions } = glue.getPartitions(
|
|
204
|
+
new GetPartitionsCommand({
|
|
205
|
+
DatabaseName: "site_logs",
|
|
206
|
+
TableName: "access_logs",
|
|
207
|
+
}),
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
// 0
|
|
211
|
+
console.log(Errors.length);
|
|
212
|
+
// s3://site-logs/cloudfront/day=2026-08-26/
|
|
213
|
+
console.log(Partitions[1]?.StorageDescriptor?.Location);
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
`CreatePartition` registers one partition and `BatchCreatePartition` registers a list of them.
|
|
217
|
+
`GetPartition` reads one back by its values, and `GetPartitions` answers with a table's partitions in
|
|
218
|
+
registration order. `DeletePartition` and `BatchDeletePartition` remove them.
|
|
219
|
+
|
|
220
|
+
A batch reports what it could not do in `Errors` and carries on with the rest, the way real Glue
|
|
221
|
+
does. Each entry there carries the values it was given and an `ErrorCode` naming the refusal, so a
|
|
222
|
+
job re-run over a week of days learns which days were already registered and registers the others.
|
|
223
|
+
|
|
224
|
+
Values are positional. A `Values` list of a different length from the table's `PartitionKeys` lines
|
|
225
|
+
up with the wrong keys, and is refused with `InvalidInputException` naming both counts. Registering
|
|
226
|
+
one day twice is an `AlreadyExistsException`, since registration is not idempotent on real Glue.
|
|
227
|
+
Deleting a table removes the partitions registered against it, the way deleting a database removes
|
|
228
|
+
its tables.
|
|
229
|
+
|
|
230
|
+
## Filtering partitions
|
|
231
|
+
|
|
232
|
+
`GetPartitions` takes an `Expression` and answers with the partitions it matches. A request carrying
|
|
233
|
+
none reads them all.
|
|
234
|
+
|
|
235
|
+
```typescript sim-glue-partition-expressions
|
|
236
|
+
/**
|
|
237
|
+
* Reading back the partitions an expression matches.
|
|
238
|
+
*/
|
|
239
|
+
|
|
240
|
+
import {
|
|
241
|
+
BatchCreatePartitionCommand,
|
|
242
|
+
CreateDatabaseCommand,
|
|
243
|
+
CreateTableCommand,
|
|
244
|
+
GetPartitionsCommand,
|
|
245
|
+
} from "@aws-sdk/client-glue";
|
|
246
|
+
|
|
247
|
+
import { SimAws } from "@kensio/yulin";
|
|
248
|
+
|
|
249
|
+
const simAws = new SimAws();
|
|
250
|
+
const glue = simAws.glue();
|
|
251
|
+
|
|
252
|
+
glue.createDatabase(
|
|
253
|
+
new CreateDatabaseCommand({ DatabaseInput: { Name: "site_logs" } }),
|
|
254
|
+
);
|
|
255
|
+
glue.createTable(
|
|
256
|
+
new CreateTableCommand({
|
|
257
|
+
DatabaseName: "site_logs",
|
|
258
|
+
TableInput: {
|
|
259
|
+
Name: "access_logs",
|
|
260
|
+
PartitionKeys: [
|
|
261
|
+
{ Name: "day", Type: "string" },
|
|
262
|
+
{ Name: "region", Type: "string" },
|
|
263
|
+
],
|
|
264
|
+
},
|
|
265
|
+
}),
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
glue.batchCreatePartition(
|
|
269
|
+
new BatchCreatePartitionCommand({
|
|
270
|
+
DatabaseName: "site_logs",
|
|
271
|
+
TableName: "access_logs",
|
|
272
|
+
PartitionInputList: [
|
|
273
|
+
{ Values: ["2026-07-31", "eu-west-2"] },
|
|
274
|
+
{ Values: ["2026-08-01", "eu-west-2"] },
|
|
275
|
+
{ Values: ["2026-08-02", "us-east-1"] },
|
|
276
|
+
],
|
|
277
|
+
}),
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const { Partitions } = glue.getPartitions(
|
|
281
|
+
new GetPartitionsCommand({
|
|
282
|
+
DatabaseName: "site_logs",
|
|
283
|
+
TableName: "access_logs",
|
|
284
|
+
Expression: "day >= '2026-08-01' AND region IN ('eu-west-2', 'us-east-1')",
|
|
285
|
+
}),
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// [["2026-08-01","eu-west-2"],["2026-08-02","us-east-1"]]
|
|
289
|
+
console.log(JSON.stringify(Partitions.map((partition) => partition.Values)));
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
A term names a partition key, an operator, and whatever the operator takes. The operators are `=`,
|
|
293
|
+
`<>` and `!=`, `>`, `<`, `>=`, `<=`, `LIKE`, `IN` and `BETWEEN`, and `BETWEEN` takes both of its
|
|
294
|
+
ends. `AND`, `OR` and `NOT` combine terms and brackets group them, with the precedence SQL gives
|
|
295
|
+
them (`NOT` binds tightest, then `AND`, then `OR`). `NOT` also sits in front of `LIKE`, `IN` and
|
|
296
|
+
`BETWEEN` to reverse that one term.
|
|
297
|
+
|
|
298
|
+
Comparison follows the type the table declares for the key. `tinyint`, `smallint`, `int`, `integer`,
|
|
299
|
+
`bigint`, `float`, `double` and `decimal` compare as numbers, so `10` sorts above `9` rather than
|
|
300
|
+
below it. Every other declared type compares as text, which is the order a date written the ISO way
|
|
301
|
+
already has. A key declared as a number is held to a numeric literal, and `hour = 'noon'` is refused
|
|
302
|
+
rather than matching nothing.
|
|
303
|
+
|
|
304
|
+
`LIKE` matches the value's text whatever type the key is declared with, since that is what `LIKE`
|
|
305
|
+
does. `%` stands for any run of characters and `_` for exactly one.
|
|
306
|
+
|
|
307
|
+
An expression naming a column the table does not partition by is refused, naming the column and the
|
|
308
|
+
keys the table does have. So is one that cannot be read, and the message says where reading stopped.
|
|
309
|
+
|
|
310
|
+
## Intercepting a GlueClient
|
|
311
|
+
|
|
312
|
+
An intercepted `GlueClient` reaches the simulated catalog, so code under test builds its own client
|
|
313
|
+
and sends its own Commands.
|
|
314
|
+
|
|
315
|
+
```typescript sim-glue-sdk-interception
|
|
316
|
+
/**
|
|
317
|
+
* Ordinary Glue SDK code reaching the simulated catalog.
|
|
318
|
+
*/
|
|
319
|
+
|
|
320
|
+
import {
|
|
321
|
+
CreateDatabaseCommand,
|
|
322
|
+
CreateTableCommand,
|
|
323
|
+
GetTablesCommand,
|
|
324
|
+
GlueClient,
|
|
325
|
+
} from "@aws-sdk/client-glue";
|
|
326
|
+
|
|
327
|
+
import { SimSdk } from "@kensio/yulin/sdk";
|
|
328
|
+
|
|
329
|
+
using simSdk = new SimSdk();
|
|
330
|
+
simSdk.intercept(GlueClient);
|
|
331
|
+
|
|
332
|
+
const client = new GlueClient({ region: "eu-west-2" });
|
|
333
|
+
|
|
334
|
+
await client.send(
|
|
335
|
+
new CreateDatabaseCommand({ DatabaseInput: { Name: "site_logs" } }),
|
|
336
|
+
);
|
|
337
|
+
await client.send(
|
|
338
|
+
new CreateTableCommand({
|
|
339
|
+
DatabaseName: "site_logs",
|
|
340
|
+
TableInput: { Name: "access_logs" },
|
|
341
|
+
}),
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
const { TableList } = await client.send(
|
|
345
|
+
new GetTablesCommand({ DatabaseName: "site_logs" }),
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
// access_logs
|
|
349
|
+
console.log(TableList?.[0]?.Name);
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## Permissions
|
|
353
|
+
|
|
354
|
+
Every Command authorizes through simulated IAM. Data Catalog resources are a hierarchy with the
|
|
355
|
+
catalog at the root, and an operation on one needs permission on that resource and on every ancestor
|
|
356
|
+
of it. Reading a table needs the table, the database and the catalog, and a policy naming only the
|
|
357
|
+
table ARN is denied. Deleting a database needs permission on every table in it as well, since the
|
|
358
|
+
tables go with it.
|
|
359
|
+
|
|
360
|
+
```typescript sim-glue-permissions
|
|
361
|
+
/**
|
|
362
|
+
* A Role that may read one table and nothing else in the catalog.
|
|
363
|
+
*/
|
|
364
|
+
|
|
365
|
+
import {
|
|
366
|
+
CreateDatabaseCommand,
|
|
367
|
+
CreateTableCommand,
|
|
368
|
+
GetTableCommand,
|
|
369
|
+
} from "@aws-sdk/client-glue";
|
|
370
|
+
import { CreateRoleCommand, PutRolePolicyCommand } from "@aws-sdk/client-iam";
|
|
371
|
+
|
|
372
|
+
import { SimAws } from "@kensio/yulin";
|
|
373
|
+
|
|
374
|
+
const simAws = new SimAws({ defaultAccountId: "111111111111" });
|
|
375
|
+
const glue = simAws.glue();
|
|
376
|
+
|
|
377
|
+
glue.createDatabase(
|
|
378
|
+
new CreateDatabaseCommand({ DatabaseInput: { Name: "site_logs" } }),
|
|
379
|
+
);
|
|
380
|
+
glue.createTable(
|
|
381
|
+
new CreateTableCommand({
|
|
382
|
+
DatabaseName: "site_logs",
|
|
383
|
+
TableInput: { Name: "access_logs" },
|
|
384
|
+
}),
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
await simAws.iam().createRole(
|
|
388
|
+
new CreateRoleCommand({
|
|
389
|
+
RoleName: "ReportingRole",
|
|
390
|
+
AssumeRolePolicyDocument: JSON.stringify({
|
|
391
|
+
Version: "2012-10-17",
|
|
392
|
+
Statement: [
|
|
393
|
+
{
|
|
394
|
+
Effect: "Allow",
|
|
395
|
+
Principal: { Service: "lambda.amazonaws.com" },
|
|
396
|
+
Action: "sts:AssumeRole",
|
|
397
|
+
},
|
|
398
|
+
],
|
|
399
|
+
}),
|
|
400
|
+
}),
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
await simAws.iam().putRolePolicy(
|
|
404
|
+
new PutRolePolicyCommand({
|
|
405
|
+
RoleName: "ReportingRole",
|
|
406
|
+
PolicyName: "ReadAccessLogs",
|
|
407
|
+
PolicyDocument: JSON.stringify({
|
|
408
|
+
Version: "2012-10-17",
|
|
409
|
+
Statement: [
|
|
410
|
+
{
|
|
411
|
+
Effect: "Allow",
|
|
412
|
+
Action: "glue:GetTable",
|
|
413
|
+
Resource: [
|
|
414
|
+
"arn:aws:glue:us-east-1:111111111111:catalog",
|
|
415
|
+
"arn:aws:glue:us-east-1:111111111111:database/site_logs",
|
|
416
|
+
"arn:aws:glue:us-east-1:111111111111:table/site_logs/access_logs",
|
|
417
|
+
],
|
|
418
|
+
},
|
|
419
|
+
],
|
|
420
|
+
}),
|
|
421
|
+
}),
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
const { Table } = glue.getTable(
|
|
425
|
+
new GetTableCommand({ DatabaseName: "site_logs", Name: "access_logs" }),
|
|
426
|
+
{
|
|
427
|
+
caller: {
|
|
428
|
+
kind: "arn",
|
|
429
|
+
arn: "arn:aws:iam::111111111111:role/ReportingRole",
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
// access_logs
|
|
435
|
+
console.log(Table.Name);
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
A policy listing only the table ARN is refused here, and refused by real Glue for the same reason.
|
|
439
|
+
|
|
440
|
+
## Available functionality
|
|
441
|
+
|
|
442
|
+
- `CreateDatabase`, `GetDatabase`, `GetDatabases` and `DeleteDatabase`.
|
|
443
|
+
- `CreateTable`, `GetTable`, `GetTables` and `DeleteTable`.
|
|
444
|
+
- `CreatePartition`, `BatchCreatePartition`, `GetPartition`, `GetPartitions`, `DeletePartition` and
|
|
445
|
+
`BatchDeletePartition`.
|
|
446
|
+
- `GetPartitions` `Expression` filtering, over the SQL operators, `AND`, `OR`, `NOT` and brackets.
|
|
447
|
+
- `AWS::Glue::Database` and `AWS::Glue::Table`, deployed and deleted with the stack.
|
|
448
|
+
- `TableInput.Parameters`, `PartitionKeys` and `StorageDescriptor`, held and read back as declared.
|
|
449
|
+
- IAM authorization on every Command, over the resource and its ancestors.
|
|
450
|
+
- Database and table names folded to lower case, as the Data Catalog folds them.
|
|
451
|
+
|
|
452
|
+
## Limitations
|
|
453
|
+
|
|
454
|
+
- **Partition projection is held here and evaluated by Athena.** The catalog stores the parameters
|
|
455
|
+
as they were declared and materializes no partition from them. Simulated
|
|
456
|
+
[Athena](https://yulinsim.dev/services/athena/ "Simulated Athena usage docs") expands them when a
|
|
457
|
+
query runs, which is where a broken projection is refused.
|
|
458
|
+
- **The catalog reads no query and no object.** SQL reaches Athena rather than the catalog, no S3
|
|
459
|
+
prefix is listed here, and no object is opened.
|
|
460
|
+
- **`AWS::Glue::Crawler` is absent.** A crawler fills a catalog by reading objects, and every object
|
|
461
|
+
stays unread here. A template declaring one deploys, with the crawler recorded on the stack's
|
|
462
|
+
`skippedResources`.
|
|
463
|
+
- **A registered partition is read by Athena, and a projected one wins over it.** Simulated
|
|
464
|
+
[Athena](https://yulinsim.dev/services/athena/ "Simulated Athena usage docs") reads a table's
|
|
465
|
+
registered partitions when a query runs, unless `projection.enabled` is true. Real Athena stops
|
|
466
|
+
reading the catalog's partitions once projection is on.
|
|
467
|
+
- **A `GetPartitions` `Expression` is the whole of the filtering.** `Segment` and parallel listing,
|
|
468
|
+
`ExcludeColumnSchema` and `TransactionId` are absent, and so are partition indexes, which change
|
|
469
|
+
which expressions real Glue will serve rather than what any of them mean.
|
|
470
|
+
- **An expression compares a partition key against a literal.** That is the whole grammar.
|
|
471
|
+
Functions, arithmetic and comparing one column against another sit outside it, and a column name
|
|
472
|
+
is written unquoted.
|
|
473
|
+
- **A partition keeps what it was registered with.** `UpdatePartition` and `BatchUpdatePartition`
|
|
474
|
+
are absent, along with `BatchGetPartition` and partition indexes.
|
|
475
|
+
- **`AWS::Glue::Partition` is absent.** Real CloudFormation has the resource type, and a template
|
|
476
|
+
declaring one deploys here with the partition recorded on the stack's `skippedResources`. A stack
|
|
477
|
+
that needs its partitions registered does it through the SDK once the deploy finishes.
|
|
478
|
+
- **A table keeps the definition it was created with.** `UpdateTable` and `UpdateDatabase` are
|
|
479
|
+
absent, and `GetTable` reports the creation time as the update time.
|
|
480
|
+
- **`Fn::GetAtt Id` on a table answers with a guess.** It resolves to the catalog id, the database
|
|
481
|
+
name and the table name joined with `|`, as in `111111111111|site_logs|access_logs`. This is the
|
|
482
|
+
one piece of behaviour here that nothing has checked against AWS. CloudFormation documents that
|
|
483
|
+
the attribute exists and documents nothing about its value, so a template asserting on it agrees
|
|
484
|
+
with this simulation and may disagree with a real deploy. Confirming it takes one stack deployed
|
|
485
|
+
to an account with `!GetAtt Table.Id` as an output.
|
|
486
|
+
- **A lookup finds a name under any spelling.** Real Glue documents the fold on the way in and asks
|
|
487
|
+
the caller to pass lower case on the way back out. `GetDatabase` and `GetTable` fold the name they
|
|
488
|
+
are given here, so a request naming `Rainlytics` reaches the database stored as `rainlytics`. This
|
|
489
|
+
is the one piece of the fold nothing has checked against AWS. A test relying on it agrees with
|
|
490
|
+
this simulation and may disagree with a real call, and passing the name in lower case avoids the
|
|
491
|
+
question.
|
|
492
|
+
- **Cross-account catalogs are refused.** A `CatalogId` naming another account is refused, in a
|
|
493
|
+
Command and in a template.
|
|
494
|
+
- **Versions, statistics and Lake Formation are absent.** A table has no version history and no
|
|
495
|
+
column statistics, and IAM is the whole of the permission model.
|
|
496
|
+
- **Iceberg and other open table formats are recorded, never built.** `OpenTableFormatInput` is
|
|
497
|
+
reported as ignored and the table is created as an ordinary one.
|
|
498
|
+
- **Listings come back whole.** `GetDatabases`, `GetTables` and `GetPartitions` answer with
|
|
499
|
+
everything in creation order. `MaxResults` and `NextToken` are absent.
|
|
500
|
+
- **Connections, jobs, triggers, workflows and the Schema Registry are absent.** The Data Catalog is
|
|
501
|
+
the whole of the simulated surface.
|