@reventlessdev/reventless-aws 3.0.0-alpha.319 → 3.0.0-alpha.321
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/CHANGELOG.md +30 -0
- package/package.json +12 -8
- package/rescript.json +4 -0
- package/run-provision-identity.mjs +3 -0
- package/scripts/ProvisionIdentity.res +303 -0
- package/scripts/ProvisionIdentity.res.mjs +376 -0
- package/src/Platform.res +54 -30
- package/src/Platform.res.mjs +34 -32
- package/src/Platform_Stack.res +102 -19
- package/src/Platform_Stack.res.mjs +43 -7
- package/src/adapter/Auth/Auth_ActiveRolePoolAttachment.res +267 -32
- package/src/adapter/Auth/Auth_ActiveRolePoolAttachment.res.mjs +124 -14
- package/src/adapter/Auth/Auth_ActiveRoleStore.res +93 -5
- package/src/adapter/Auth/Auth_ActiveRoleStore.res.mjs +53 -4
- package/src/adapter/Auth/Auth_ActiveRoleStore_Ops.res +50 -4
- package/src/adapter/Auth/Auth_ActiveRoleStore_Ops.res.mjs +24 -3
- package/src/adapter/Auth/Auth_ActiveRoleStore_Schema.res +92 -0
- package/src/adapter/Auth/Auth_ActiveRoleStore_Schema.res.mjs +49 -0
- package/src/adapter/Auth/Auth_ActiveRoleTrigger_Ops.res +21 -7
- package/src/adapter/Auth/Auth_ActiveRoleTrigger_Ops.res.mjs +15 -7
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +34 -18
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +14 -4
- package/src/adapter/QueryDb/QueryDbStorage_DynamoDb.res +29 -14
- package/src/adapter/QueryDb/QueryDbStorage_DynamoDb.res.mjs +11 -1
- package/src/util/Util_AwsError.res +56 -0
- package/src/util/Util_AwsError.res.mjs +59 -0
- package/src/util/Util_ShellConfig.res +28 -0
- package/src/util/Util_ShellConfig.res.mjs +22 -0
- package/tests/AppSync_RetirementNarrowingTest.res +10 -12
- package/tests/AppSync_RetirementNarrowingTest.res.mjs +4 -2
- package/tests/Auth_ActiveRolePoolAttachmentTest.res +175 -0
- package/tests/Auth_ActiveRolePoolAttachmentTest.res.mjs +136 -0
- package/tests/Auth_ActiveRoleStoreTest.res +65 -0
- package/tests/Auth_ActiveRoleStoreTest.res.mjs +48 -0
- package/tests/Auth_ActiveRoleStore_SchemaTest.res +98 -0
- package/tests/Auth_ActiveRoleStore_SchemaTest.res.mjs +97 -0
- package/tests/AwsErrorFixtures.mjs +16 -0
- package/tests/ProvisionIdentityTest.res +109 -0
- package/tests/ProvisionIdentityTest.res.mjs +132 -0
- package/tests/QueryDbOwnerIndexTableTest.res +53 -0
- package/tests/QueryDbOwnerIndexTableTest.res.mjs +50 -0
- package/tests/QueryDbResolvers_AppSyncTest.res +19 -26
- package/tests/QueryDbResolvers_AppSyncTest.res.mjs +15 -17
- package/tests/Util_AwsErrorTest.res +127 -0
- package/tests/Util_AwsErrorTest.res.mjs +76 -0
- package/tests/Util_ShellConfigTest.res +51 -0
- package/tests/Util_ShellConfigTest.res.mjs +42 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,36 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.321 (2026-08-23)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **aws:** serve a full page from a filtered AppSync read ([b7dca5d](https://github.com/ReventlessDev/reventless-core/commit/b7dca5dac02e8c35c4848828152b90ade0f3c8ba))
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **querydb:** serve owner-scoped lists from an index instead of a filtered Scan ([ad26c28](https://github.com/ReventlessDev/reventless-core/commit/ad26c284b4b0650fa3f3b6109ed33bbfe8608f6d))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# 3.0.0-alpha.320 (2026-08-23)
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* **aws:** make the identity provisioning script reachable and legible ([a5e7059](https://github.com/ReventlessDev/reventless-core/commit/a5e7059044cf8ab65a1dea92b0d70cbd6d306717))
|
|
21
|
+
* feat(aws)!: scope the active-role store to the identity provider ([08e287d](https://github.com/ReventlessDev/reventless-core/commit/08e287db9721977b818bad76671ec4c62be6f9af))
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* **aws:** publish identity names alongside the cognito ones ([e240d4f](https://github.com/ReventlessDev/reventless-core/commit/e240d4f01fbda20b71edf822596e634b0d4cdbc0))
|
|
25
|
+
|
|
26
|
+
### BREAKING CHANGES
|
|
27
|
+
|
|
28
|
+
* platform:cognitoUserPoolId is now
|
|
29
|
+
platform:identityProviderId (REVENTLESS_IDENTITY_PROVIDER_ID). The old
|
|
30
|
+
spelling is still read and warns — do not drop it until the new secret
|
|
31
|
+
is in place, because an unset provider id is auto mode, and auto mode
|
|
32
|
+
creates a new user pool.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
6
36
|
# 3.0.0-alpha.319 (2026-08-22)
|
|
7
37
|
|
|
8
38
|
* feat(core)!: a slice that has given up says so ([f5559ab](https://github.com/ReventlessDev/reventless-core/commit/f5559abede3a9cf2c9a48aadc69a666c46cc75bf))
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-aws",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.321",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
+
"bin": {
|
|
7
|
+
"provision-identity": "./run-provision-identity.mjs"
|
|
8
|
+
},
|
|
6
9
|
"dependencies": {
|
|
7
10
|
"@aws-sdk/client-appsync": "3.970.0",
|
|
8
11
|
"@aws-sdk/client-cloudfront": "3.970.0",
|
|
@@ -12,18 +15,18 @@
|
|
|
12
15
|
"@aws-sdk/s3-request-presigner": "3.970.0",
|
|
13
16
|
"sury": "11.0.0-rc.2",
|
|
14
17
|
"uuid": "^13.0.0",
|
|
15
|
-
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.
|
|
18
|
+
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.13",
|
|
16
19
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
17
|
-
"@reventlessdev/rescript-
|
|
18
|
-
"@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.2",
|
|
20
|
+
"@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.4",
|
|
19
21
|
"@reventlessdev/rescript-node": "2.0.0-alpha.8",
|
|
22
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
20
23
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
|
|
21
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.247",
|
|
22
24
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
23
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.
|
|
25
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.151",
|
|
24
26
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.33",
|
|
25
|
-
"@reventlessdev/reventless-
|
|
26
|
-
"@reventlessdev/reventless-postgres": "3.0.0-alpha.
|
|
27
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.249",
|
|
28
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.113",
|
|
29
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.123"
|
|
27
30
|
},
|
|
28
31
|
"devDependencies": {
|
|
29
32
|
"rescript": "12.3.0",
|
|
@@ -69,6 +72,7 @@
|
|
|
69
72
|
"clean": "rescript clean",
|
|
70
73
|
"rebuild": "pnpm run clean && pnpm run build",
|
|
71
74
|
"deploy": "./scripts/deploy.sh",
|
|
75
|
+
"provision:identity": "node ./run-provision-identity.mjs",
|
|
72
76
|
"test": "NODE_OPTIONS='--experimental-vm-modules' npx jest",
|
|
73
77
|
"coverage": "nyc report --temp-directory=coverage --reporter=text-lcov | coveralls",
|
|
74
78
|
"dev": "jest --watchAll",
|
package/rescript.json
CHANGED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/***
|
|
2
|
+
Provision an identity provider that no Pulumi stack owns, and the active-role
|
|
3
|
+
store that belongs to it.
|
|
4
|
+
|
|
5
|
+
This is the BYO half of `Platform_Stack.resolveCognitoUserPool`. A stack given
|
|
6
|
+
`platform:identityProviderId` creates neither the pool nor the store — it looks
|
|
7
|
+
both up, and fails if either is missing. Something has to make them, once,
|
|
8
|
+
outside every stack. That is this.
|
|
9
|
+
|
|
10
|
+
🚨 **It does not attach a pre-token-generation trigger, and must not.** The pool's
|
|
11
|
+
one trigger slot is the deploy's business: a script that filled it would be the
|
|
12
|
+
hand-run `UpdateUserPool` this whole area refuses, leaving the deployment's
|
|
13
|
+
behaviour depending on an act no source describes.
|
|
14
|
+
|
|
15
|
+
The store's name and key schema come from [Auth_ActiveRoleStore_Schema] rather
|
|
16
|
+
than from constants here. A second copy of either would be one edit away from
|
|
17
|
+
creating a table the deploy does not look for — which surfaces as "table not
|
|
18
|
+
found" long after whoever ran this has moved on.
|
|
19
|
+
|
|
20
|
+
Reached as the `provision-identity` bin, from any package depending on
|
|
21
|
+
`@reventlessdev/reventless-aws` — an app's `platform-aws`, typically:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
pnpm exec provision-identity --name MyIdentity
|
|
25
|
+
pnpm exec provision-identity --provider-id eu-west-1_AbCdEfGhI
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Re-running is safe: existing resources are adopted and reported, never recreated.
|
|
29
|
+
The first thing anyone does with a provisioning script is run it twice.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
open AwsSdk
|
|
33
|
+
|
|
34
|
+
module Schema = Auth_ActiveRoleStore_Schema
|
|
35
|
+
|
|
36
|
+
// ── Arguments ────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
type args = {
|
|
39
|
+
poolName: string,
|
|
40
|
+
providerId: option<string>,
|
|
41
|
+
help: bool,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let defaultPoolName = "ReventlessIdentity"
|
|
45
|
+
|
|
46
|
+
/** Parsed rather than positional, and unknown flags are an error: a typo'd
|
|
47
|
+
`--provider-id` that parsed as "absent" would create a *second* pool beside the
|
|
48
|
+
one the operator meant to extend. */
|
|
49
|
+
let parseArgs = (argv: array<string>): result<args, string> => {
|
|
50
|
+
let acc = ref(Ok({poolName: defaultPoolName, providerId: None, help: false}))
|
|
51
|
+
let i = ref(0)
|
|
52
|
+
let count = argv->Array.length
|
|
53
|
+
while i.contents < count {
|
|
54
|
+
let flag = argv->Array.getUnsafe(i.contents)
|
|
55
|
+
let value = argv->Array.get(i.contents + 1)
|
|
56
|
+
switch (acc.contents, flag, value) {
|
|
57
|
+
| (Error(_), _, _) => i := count
|
|
58
|
+
| (Ok(a), "--name", Some(v)) =>
|
|
59
|
+
acc := Ok({...a, poolName: v})
|
|
60
|
+
i := i.contents + 2
|
|
61
|
+
| (Ok(a), "--provider-id", Some(v)) =>
|
|
62
|
+
acc := Ok({...a, providerId: Some(v)})
|
|
63
|
+
i := i.contents + 2
|
|
64
|
+
| (Ok(a), "--help", _) | (Ok(a), "-h", _) =>
|
|
65
|
+
acc := Ok({...a, help: true})
|
|
66
|
+
i := i.contents + 1
|
|
67
|
+
| (Ok(_), "--name", None) | (Ok(_), "--provider-id", None) =>
|
|
68
|
+
acc := Error(`${flag} needs a value`)
|
|
69
|
+
| (Ok(_), unknown, _) => acc := Error(`unknown argument "${unknown}"`)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
acc.contents
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let usage = `
|
|
76
|
+
Provision a Reventless identity provider and its active-role store.
|
|
77
|
+
|
|
78
|
+
--name <name> Pool name to create or adopt (default: ${defaultPoolName})
|
|
79
|
+
--provider-id <id> Use this existing pool instead of creating one; only the
|
|
80
|
+
store is provisioned
|
|
81
|
+
|
|
82
|
+
Creates nothing that a platform stack owns, and never attaches a trigger.
|
|
83
|
+
Region and credentials come from the environment, as for any AWS SDK call.
|
|
84
|
+
`
|
|
85
|
+
|
|
86
|
+
// ── The pool ─────────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
/** SPA-friendly defaults, matching what auto mode declares: email sign-in, a
|
|
89
|
+
12-character password policy, no MFA, admin-only user creation. An operator
|
|
90
|
+
wanting something else edits the pool afterwards rather than having this script
|
|
91
|
+
grow a flag per Cognito setting. */
|
|
92
|
+
let poolSettings = (~poolName: string): CognitoIdentityServiceProvider.CreateUserPoolCommand.input => {
|
|
93
|
+
poolName,
|
|
94
|
+
usernameAttributes: ["email"],
|
|
95
|
+
mfaConfiguration: "OFF",
|
|
96
|
+
adminCreateUserConfig: {allowAdminCreateUserOnly: true},
|
|
97
|
+
policies: {
|
|
98
|
+
passwordPolicy: {
|
|
99
|
+
minimumLength: 12,
|
|
100
|
+
requireLowercase: true,
|
|
101
|
+
requireUppercase: true,
|
|
102
|
+
requireNumbers: true,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** The pool with this name, if exactly one has it.
|
|
108
|
+
|
|
109
|
+
Cognito does not enforce unique pool names and offers no lookup by name, so
|
|
110
|
+
adoption means paging the list. Without this a second run silently creates a
|
|
111
|
+
second pool with the same name, and the operator then has two with no way to
|
|
112
|
+
tell from the console which one their stacks are configured for.
|
|
113
|
+
|
|
114
|
+
Two matches is an error rather than a choice — picking either would be a guess
|
|
115
|
+
at which one holds the accounts. */
|
|
116
|
+
let findPoolByName = async (~poolName: string): result<option<string>, string> => {
|
|
117
|
+
let nextToken = ref(None)
|
|
118
|
+
let more = ref(true)
|
|
119
|
+
let found = []
|
|
120
|
+
while more.contents {
|
|
121
|
+
let page = await CognitoIdentityServiceProvider.ListUserPoolsCommand.make({
|
|
122
|
+
maxResults: 60,
|
|
123
|
+
nextToken: ?nextToken.contents,
|
|
124
|
+
})->CognitoIdentityServiceProvider.ListUserPoolsCommand.send
|
|
125
|
+
page.userPools
|
|
126
|
+
->Option.getOr([])
|
|
127
|
+
->Array.forEach(p =>
|
|
128
|
+
switch (p.name, p.id) {
|
|
129
|
+
| (Some(name), Some(id)) if name == poolName => found->Array.push(id)
|
|
130
|
+
| _ => ()
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
switch page.nextToken {
|
|
134
|
+
| Some(_) as t => nextToken := t
|
|
135
|
+
| None => more := false
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
switch found {
|
|
139
|
+
| [] => Ok(None)
|
|
140
|
+
| [only] => Ok(Some(only))
|
|
141
|
+
| several =>
|
|
142
|
+
Error(
|
|
143
|
+
`${several->Array.length->Int.toString} user pools are named "${poolName}" (${several->Array.join(
|
|
144
|
+
", ",
|
|
145
|
+
)}). Pass --provider-id to say which one to use; this script will not guess.`,
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let resolvePool = async (~args: args): result<string, string> =>
|
|
151
|
+
switch args.providerId {
|
|
152
|
+
| Some(id) =>
|
|
153
|
+
// Described rather than trusted: the store's name is derived from this id, so
|
|
154
|
+
// a typo would produce a correctly-created table that nothing ever reads.
|
|
155
|
+
let _ = await CognitoIdentityServiceProvider.DescribeUserPoolCommand.make({
|
|
156
|
+
userPoolId: id,
|
|
157
|
+
})->CognitoIdentityServiceProvider.DescribeUserPoolCommand.send
|
|
158
|
+
Console.log(`pool ${id} (given, unchanged)`)
|
|
159
|
+
Ok(id)
|
|
160
|
+
| None =>
|
|
161
|
+
switch await findPoolByName(~poolName=args.poolName) {
|
|
162
|
+
| Error(_) as e => e
|
|
163
|
+
| Ok(Some(existing)) =>
|
|
164
|
+
Console.log(`pool ${existing} (adopted, named "${args.poolName}")`)
|
|
165
|
+
Ok(existing)
|
|
166
|
+
| Ok(None) =>
|
|
167
|
+
let created = await CognitoIdentityServiceProvider.CreateUserPoolCommand.make(
|
|
168
|
+
poolSettings(~poolName=args.poolName),
|
|
169
|
+
)->CognitoIdentityServiceProvider.CreateUserPoolCommand.send
|
|
170
|
+
switch created.userPool->Option.flatMap(p => p.id) {
|
|
171
|
+
| None => Error("CreateUserPool returned no pool id")
|
|
172
|
+
| Some(id) =>
|
|
173
|
+
Console.log(`pool ${id} (created, named "${args.poolName}")`)
|
|
174
|
+
Ok(id)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── The store ────────────────────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
let keySchema: array<DynamoDb.DynamoDb.DescribeTableCommand.keySchemaElement> = [
|
|
182
|
+
{attributeName: Schema.partitionKey, keyType: "HASH"},
|
|
183
|
+
{attributeName: Schema.sortKey, keyType: "RANGE"},
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
/** The SDK's shape mapped onto the plain pairs [Schema.keySchemaRefusal] compares.
|
|
187
|
+
The refusal itself lives with the schema it is about, since the schema is what
|
|
188
|
+
it is a statement about — and it is shared with the deploy, which never sees
|
|
189
|
+
this file. */
|
|
190
|
+
let asPairs = (elements: array<DynamoDb.DynamoDb.DescribeTableCommand.keySchemaElement>) =>
|
|
191
|
+
elements->Array.map(k => (k.attributeName, k.keyType))
|
|
192
|
+
|
|
193
|
+
let describeTable = async (~tableName: string): option<
|
|
194
|
+
DynamoDb.DynamoDb.DescribeTableCommand.tableDescription,
|
|
195
|
+
> =>
|
|
196
|
+
try {
|
|
197
|
+
let out = await DynamoDb.DynamoDb.DescribeTableCommand.make({
|
|
198
|
+
tableName: tableName,
|
|
199
|
+
})->DynamoDb.DynamoDb.DescribeTableCommand.send
|
|
200
|
+
out.table
|
|
201
|
+
} catch {
|
|
202
|
+
| exn if Util_AwsError.isNotFound(exn) => None
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let provisionStore = async (~tableName: string): result<unit, string> =>
|
|
206
|
+
switch await describeTable(~tableName) {
|
|
207
|
+
| Some(described) =>
|
|
208
|
+
switch Schema.keySchemaRefusal(
|
|
209
|
+
~tableName,
|
|
210
|
+
~actual=described.keySchema->Option.getOr([])->asPairs,
|
|
211
|
+
) {
|
|
212
|
+
| Some(message) => Error(message)
|
|
213
|
+
| None =>
|
|
214
|
+
Console.log(`store ${tableName} (adopted)`)
|
|
215
|
+
Ok()
|
|
216
|
+
}
|
|
217
|
+
| None =>
|
|
218
|
+
let _ = await DynamoDb.DynamoDb.CreateTableCommand.make({
|
|
219
|
+
tableName,
|
|
220
|
+
keySchema,
|
|
221
|
+
attributeDefinitions: [
|
|
222
|
+
{attributeName: Schema.partitionKey, attributeType: "S"},
|
|
223
|
+
{attributeName: Schema.sortKey, attributeType: "S"},
|
|
224
|
+
],
|
|
225
|
+
billingMode: "PAY_PER_REQUEST",
|
|
226
|
+
})->DynamoDb.DynamoDb.CreateTableCommand.send
|
|
227
|
+
|
|
228
|
+
let waited = await DynamoDb.DynamoDb.TableExistsWaiter.wait(~tableName)
|
|
229
|
+
if !DynamoDb.DynamoDb.TableExistsWaiter.succeeded(waited) {
|
|
230
|
+
Error(
|
|
231
|
+
`table "${tableName}" was created but did not become ACTIVE (${waited.state}). Re-run this script — it adopts what is already there.`,
|
|
232
|
+
)
|
|
233
|
+
} else {
|
|
234
|
+
// Matches what the framework's own tables carry, and it can only be turned
|
|
235
|
+
// on once the table is ACTIVE — hence the wait above.
|
|
236
|
+
let _ = await DynamoDb.DynamoDb.UpdateContinuousBackupsCommand.make({
|
|
237
|
+
tableName,
|
|
238
|
+
pointInTimeRecoverySpecification: {pointInTimeRecoveryEnabled: true},
|
|
239
|
+
})->DynamoDb.DynamoDb.UpdateContinuousBackupsCommand.send
|
|
240
|
+
Console.log(`store ${tableName} (created)`)
|
|
241
|
+
Ok()
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
/** Printed rather than assumed: nothing here owns these resources, so nothing
|
|
248
|
+
here can wire them up either. */
|
|
249
|
+
let nextSteps = (~providerId: string) => `
|
|
250
|
+
Configure each platform stack that should use this provider:
|
|
251
|
+
|
|
252
|
+
pulumi config set platform:identityProviderId ${providerId}
|
|
253
|
+
|
|
254
|
+
or set REVENTLESS_IDENTITY_PROVIDER_ID in CI. Every stack on this provider
|
|
255
|
+
derives the same store, so there is nothing else to configure.
|
|
256
|
+
|
|
257
|
+
The pre-token-generation trigger is attached by the deploy, not by this script.
|
|
258
|
+
Users and groups are yours to create.`
|
|
259
|
+
|
|
260
|
+
let run = async (): result<unit, string> =>
|
|
261
|
+
// argv[0] is node, argv[1] this script.
|
|
262
|
+
switch parseArgs(NodeProcess.argv->Array.slice(~start=2, ~end=NodeProcess.argv->Array.length)) {
|
|
263
|
+
| Error(_) as e => e
|
|
264
|
+
| Ok(args) if args.help =>
|
|
265
|
+
Console.log(usage)
|
|
266
|
+
Ok()
|
|
267
|
+
| Ok(args) =>
|
|
268
|
+
switch await resolvePool(~args) {
|
|
269
|
+
| Error(_) as e => e
|
|
270
|
+
| Ok(providerId) =>
|
|
271
|
+
let tableName = Schema.derivedStoreName(~identityProviderId=providerId)
|
|
272
|
+
switch await provisionStore(~tableName) {
|
|
273
|
+
| Error(_) as e => e
|
|
274
|
+
| Ok() =>
|
|
275
|
+
Console.log(nextSteps(~providerId))
|
|
276
|
+
Ok()
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
🚨 **Catches thrown exceptions, not only `Error` results.**
|
|
283
|
+
|
|
284
|
+
Without this an SDK failure escapes an async function nothing awaits, and Node
|
|
285
|
+
reports `UnhandledPromiseRejection ... "#<Object>"` — which names neither the call
|
|
286
|
+
that failed nor why. Every AWS call in this script can throw, so the top level has
|
|
287
|
+
to turn any of them into something an operator can read.
|
|
288
|
+
*/
|
|
289
|
+
let main = async () =>
|
|
290
|
+
switch await run() {
|
|
291
|
+
| Ok() => ()
|
|
292
|
+
| Error(message) =>
|
|
293
|
+
Console.error(`provision-identity: ${message}`)
|
|
294
|
+
NodeProcess.exit(1)
|
|
295
|
+
| exception exn =>
|
|
296
|
+
Console.error(`provision-identity: ${Util_AwsError.describe(exn)}`)
|
|
297
|
+
NodeProcess.exit(1)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// 🚨 **No top-level call.** `../run-provision-identity.mjs` invokes [main]; this
|
|
301
|
+
// module only defines it. A module that ran itself on import could not be
|
|
302
|
+
// imported by a test — which is exactly how a guard that could never match
|
|
303
|
+
// shipped in this file once already.
|