@reventlessdev/reventless-seed-aws 1.0.0-alpha.10
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 +69 -0
- package/LICENSE +202 -0
- package/lib/bs/.compiler.log +2 -0
- package/lib/bs/.sourcedirs.json +1 -0
- package/lib/bs/build.ninja +0 -0
- package/lib/bs/compiler-info.json +8 -0
- package/lib/bs/src/ReventlessSeedAws.ast +0 -0
- package/lib/bs/src/ReventlessSeedAws.cmi +0 -0
- package/lib/bs/src/ReventlessSeedAws.cmj +0 -0
- package/lib/bs/src/ReventlessSeedAws.cmt +0 -0
- package/lib/bs/src/ReventlessSeedAws.res +328 -0
- package/lib/bs/src/ReventlessSeedAws.res.mjs +309 -0
- package/lib/bs/src/ReventlessSeedAws_Reconcile.ast +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reconcile.cmi +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reconcile.cmj +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reconcile.cmt +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reconcile.res +389 -0
- package/lib/bs/src/ReventlessSeedAws_Reconcile.res.mjs +318 -0
- package/lib/bs/src/ReventlessSeedAws_Reset.ast +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reset.cmi +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reset.cmj +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reset.cmt +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reset.res +948 -0
- package/lib/bs/src/ReventlessSeedAws_Reset.res.mjs +861 -0
- package/lib/bs/tests/EndpointResolutionTest.ast +0 -0
- package/lib/bs/tests/EndpointResolutionTest.cmi +0 -0
- package/lib/bs/tests/EndpointResolutionTest.cmj +0 -0
- package/lib/bs/tests/EndpointResolutionTest.cmt +0 -0
- package/lib/bs/tests/EndpointResolutionTest.res +83 -0
- package/lib/bs/tests/EndpointResolutionTest.res.mjs +115 -0
- package/lib/bs/tests/ObjectStoreReconcileTest.ast +0 -0
- package/lib/bs/tests/ObjectStoreReconcileTest.cmi +0 -0
- package/lib/bs/tests/ObjectStoreReconcileTest.cmj +0 -0
- package/lib/bs/tests/ObjectStoreReconcileTest.cmt +0 -0
- package/lib/bs/tests/ObjectStoreReconcileTest.res +84 -0
- package/lib/bs/tests/ObjectStoreReconcileTest.res.mjs +102 -0
- package/lib/bs/tests/ObjectStoreResetTest.ast +0 -0
- package/lib/bs/tests/ObjectStoreResetTest.cmi +0 -0
- package/lib/bs/tests/ObjectStoreResetTest.cmj +0 -0
- package/lib/bs/tests/ObjectStoreResetTest.cmt +0 -0
- package/lib/bs/tests/ObjectStoreResetTest.res +170 -0
- package/lib/bs/tests/ObjectStoreResetTest.res.mjs +211 -0
- package/lib/ocaml/.compiler.log +2 -0
- package/lib/ocaml/EndpointResolutionTest.ast +0 -0
- package/lib/ocaml/EndpointResolutionTest.cmi +0 -0
- package/lib/ocaml/EndpointResolutionTest.cmj +0 -0
- package/lib/ocaml/EndpointResolutionTest.cmt +0 -0
- package/lib/ocaml/EndpointResolutionTest.res +83 -0
- package/lib/ocaml/ObjectStoreReconcileTest.ast +0 -0
- package/lib/ocaml/ObjectStoreReconcileTest.cmi +0 -0
- package/lib/ocaml/ObjectStoreReconcileTest.cmj +0 -0
- package/lib/ocaml/ObjectStoreReconcileTest.cmt +0 -0
- package/lib/ocaml/ObjectStoreReconcileTest.res +84 -0
- package/lib/ocaml/ObjectStoreResetTest.ast +0 -0
- package/lib/ocaml/ObjectStoreResetTest.cmi +0 -0
- package/lib/ocaml/ObjectStoreResetTest.cmj +0 -0
- package/lib/ocaml/ObjectStoreResetTest.cmt +0 -0
- package/lib/ocaml/ObjectStoreResetTest.res +170 -0
- package/lib/ocaml/ReventlessSeedAws.ast +0 -0
- package/lib/ocaml/ReventlessSeedAws.cmi +0 -0
- package/lib/ocaml/ReventlessSeedAws.cmj +0 -0
- package/lib/ocaml/ReventlessSeedAws.cmt +0 -0
- package/lib/ocaml/ReventlessSeedAws.res +328 -0
- package/lib/ocaml/ReventlessSeedAws_Reconcile.ast +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reconcile.cmi +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reconcile.cmj +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reconcile.cmt +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reconcile.res +389 -0
- package/lib/ocaml/ReventlessSeedAws_Reset.ast +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reset.cmi +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reset.cmj +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reset.cmt +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reset.res +948 -0
- package/package.json +32 -0
- package/rescript.json +29 -0
- package/src/ReventlessSeedAws.res +328 -0
- package/src/ReventlessSeedAws.res.mjs +309 -0
- package/src/ReventlessSeedAws_Reconcile.res +389 -0
- package/src/ReventlessSeedAws_Reconcile.res.mjs +318 -0
- package/src/ReventlessSeedAws_Reset.res +948 -0
- package/src/ReventlessSeedAws_Reset.res.mjs +861 -0
- package/tests/EndpointResolutionTest.res +83 -0
- package/tests/EndpointResolutionTest.res.mjs +115 -0
- package/tests/ObjectStoreReconcileTest.res +84 -0
- package/tests/ObjectStoreReconcileTest.res.mjs +102 -0
- package/tests/ObjectStoreResetTest.res +170 -0
- package/tests/ObjectStoreResetTest.res.mjs +211 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
// AWS `connect` for the seed harness: resolve a deployed stack, read its
|
|
2
|
+
// endpoints, and log in with Cognito — returning the same provider-agnostic
|
|
3
|
+
// `Seed.connection` a local run produces.
|
|
4
|
+
//
|
|
5
|
+
// Stack discovery reads the sibling Pulumi project's `Pulumi.<stack>.yaml`
|
|
6
|
+
// files (no `pulumi login` needed to list) and calls `pulumi stack output` for
|
|
7
|
+
// the chosen one. Endpoints come from the host-shell `config.json` when the
|
|
8
|
+
// stack publishes a `hostShellUrl` (the hybrid host-shell shape), or from the
|
|
9
|
+
// stack outputs directly otherwise. Login is Cognito USER_PASSWORD_AUTH over a
|
|
10
|
+
// plain `fetch` — the host UI client has no secret, so there is no SigV4 and no
|
|
11
|
+
// AWS SDK dependency.
|
|
12
|
+
|
|
13
|
+
open ReventlessSeed
|
|
14
|
+
|
|
15
|
+
// ── Node bindings ─────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
type fetchInit = {method: string, headers: dict<string>, body?: string}
|
|
19
|
+
type response
|
|
20
|
+
@val external fetch: (string, fetchInit) => promise<response> = "fetch"
|
|
21
|
+
@send external responseJson: response => promise<JSON.t> = "json"
|
|
22
|
+
@get external responseOk: response => bool = "ok"
|
|
23
|
+
@get external responseStatus: response => int = "status"
|
|
24
|
+
|
|
25
|
+
// ── JSON helpers ──────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
let field = (json: JSON.t, key: string): option<JSON.t> =>
|
|
28
|
+
switch json {
|
|
29
|
+
| Object(obj) => obj->Dict.get(key)
|
|
30
|
+
| _ => None
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let asString = (json: JSON.t): option<string> =>
|
|
34
|
+
switch json {
|
|
35
|
+
| String(s) => Some(s)
|
|
36
|
+
| _ => None
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── Stack discovery ───────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
// Which Pulumi backend the `pulumi` subprocess reads from. When `backend` is
|
|
42
|
+
// set it is passed as `PULUMI_BACKEND_URL` on a *copy* of the environment, so
|
|
43
|
+
// the operator's persistent `pulumi login` is never mutated — each example pins
|
|
44
|
+
// its own backend (core → Pulumi Cloud, a self-hosted app → its S3/… bucket)
|
|
45
|
+
// regardless of which backend the CLI is currently logged into. When absent, the
|
|
46
|
+
// subprocess inherits the ambient login (or an operator-set PULUMI_BACKEND_URL).
|
|
47
|
+
let envForBackend = (backend: option<string>): option<dict<string>> =>
|
|
48
|
+
switch backend {
|
|
49
|
+
| None => None
|
|
50
|
+
| Some(url) =>
|
|
51
|
+
let copy = NodeProcess.env->Dict.toArray->Dict.fromArray
|
|
52
|
+
copy->Dict.set("PULUMI_BACKEND_URL", url)
|
|
53
|
+
Some(copy)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let pulumi = (~projectDir: string, ~backend: option<string>, args: array<string>): string =>
|
|
57
|
+
NodeChildProcess.execFileSync(
|
|
58
|
+
"pulumi",
|
|
59
|
+
args,
|
|
60
|
+
{cwd: projectDir, encoding: "utf8", env: ?envForBackend(backend)},
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
// Stacks that actually exist in the Pulumi backend for this project — the names
|
|
64
|
+
// `pulumi stack output --stack <name>` accepts. A `Pulumi.<stack>.yaml` config
|
|
65
|
+
// file alone is NOT a deployed stack, so discovery must ask the backend (via
|
|
66
|
+
// `pulumi stack ls`), not the filesystem — otherwise it offers phantom stacks
|
|
67
|
+
// that fail with "no stack named …". Empty on any error (pulumi missing / not
|
|
68
|
+
// logged in / no stacks), which `resolveStack` reports as "none found".
|
|
69
|
+
let deployedStacks = (~projectDir: string, ~backend: option<string>): array<string> =>
|
|
70
|
+
try {
|
|
71
|
+
switch JSON.parseOrThrow(pulumi(~projectDir, ~backend, ["stack", "ls", "--json"])) {
|
|
72
|
+
| JSON.Array(items) => items->Array.filterMap(it => it->field("name")->Option.flatMap(asString))
|
|
73
|
+
| _ => []
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
| _ => []
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let stackOutputs = (~projectDir: string, ~backend: option<string>, stack: string): JSON.t => {
|
|
80
|
+
let raw = try pulumi(~projectDir, ~backend, ["stack", "output", "--stack", stack, "--json"]) catch {
|
|
81
|
+
| _ =>
|
|
82
|
+
throw(
|
|
83
|
+
Seed.Failed(
|
|
84
|
+
`pulumi stack output --stack ${stack} failed — is pulumi installed, logged in, ` ++
|
|
85
|
+
"and the stack deployed?",
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
try JSON.parseOrThrow(raw) catch {
|
|
90
|
+
| _ => throw(Seed.Failed(`could not parse pulumi stack output for "${stack}"`))
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let backendNote = (~backend: option<string>): string =>
|
|
95
|
+
switch backend {
|
|
96
|
+
| Some(url) => ` (backend: ${url})`
|
|
97
|
+
| None => ""
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let resolveStack = async (~projectDir: string, ~backend: option<string>, ~stack: option<string>): string =>
|
|
101
|
+
switch stack {
|
|
102
|
+
| Some(s) => s
|
|
103
|
+
| None =>
|
|
104
|
+
switch Seed.Prompt.envValue("SEED_STACK") {
|
|
105
|
+
| Some(s) => s
|
|
106
|
+
| None =>
|
|
107
|
+
let stacks = deployedStacks(~projectDir, ~backend)
|
|
108
|
+
if stacks->Array.length == 0 {
|
|
109
|
+
throw(
|
|
110
|
+
Seed.Failed(
|
|
111
|
+
`no deployed Pulumi stacks for this project (\`pulumi stack ls\` is empty in ${projectDir})${backendNote(
|
|
112
|
+
~backend,
|
|
113
|
+
)}. ` ++
|
|
114
|
+
"A Pulumi.<stack>.yaml config file alone is not a deployed stack — run `pulumi up` " ++
|
|
115
|
+
"first, log in to the backend that holds the stack, or set SEED_STACK to target one.",
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
await Seed.Prompt.select(~title="Stack:", ~options=stacks->Array.map(s => (s, s)))
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── Endpoints ─────────────────────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
let fetchConfig = async (hostShellUrl: string): JSON.t => {
|
|
126
|
+
let base = hostShellUrl->String.replaceRegExp(%re("/\/+$/g"), "")
|
|
127
|
+
let url = `${base}/config.json`
|
|
128
|
+
let res = try await fetch(url, {method: "GET", headers: Dict.make()}) catch {
|
|
129
|
+
| _ => throw(Seed.Failed(`cannot reach ${url}`))
|
|
130
|
+
}
|
|
131
|
+
if !(res->responseOk) {
|
|
132
|
+
throw(Seed.Failed(`GET ${url} → HTTP ${(res->responseStatus)->Int.toString}`))
|
|
133
|
+
}
|
|
134
|
+
await res->responseJson
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Prefer an explicit env override, else the discovered value, else fail naming
|
|
138
|
+
// what is missing.
|
|
139
|
+
let resolveField = (~envKey: string, ~fromSource: option<string>, ~human: string): string =>
|
|
140
|
+
switch Seed.Prompt.envValue(envKey) {
|
|
141
|
+
| Some(v) => v
|
|
142
|
+
| None =>
|
|
143
|
+
switch fromSource {
|
|
144
|
+
| Some(v) => v
|
|
145
|
+
| None => throw(Seed.Failed(`deployment is missing ${human} (and ${envKey} is unset)`))
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Which document publishes the deployment's endpoints. A stack that serves a
|
|
150
|
+
// host shell publishes them in the shell's `config.json` under the client's key
|
|
151
|
+
// names; one that does not publishes them as stack outputs under Pulumi's. The
|
|
152
|
+
// two arms name different keys for the same values, which is why this is a
|
|
153
|
+
// variant and not a merged lookup.
|
|
154
|
+
type source = HostShellConfig(JSON.t) | StackOutputs(JSON.t)
|
|
155
|
+
|
|
156
|
+
type endpoints = {
|
|
157
|
+
graphql: string,
|
|
158
|
+
cognitoRegion: string,
|
|
159
|
+
cognitoClientId: string,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The endpoints a source document publishes — pure, so the branch selection and
|
|
164
|
+
* every key it reads can be exercised with synthetic documents.
|
|
165
|
+
*
|
|
166
|
+
* Upload endpoints are no longer resolved here: under route B the seed mints through
|
|
167
|
+
* the domain API's `Upload_Presign` mutation on `graphql`, passing the store per asset,
|
|
168
|
+
* so there is no per-store URL to discover.
|
|
169
|
+
*/
|
|
170
|
+
let endpointsFrom = (~stack: string, source: source): endpoints =>
|
|
171
|
+
switch source {
|
|
172
|
+
| HostShellConfig(cfg) =>
|
|
173
|
+
let fromCfg = key => cfg->field(key)->Option.flatMap(asString)
|
|
174
|
+
{
|
|
175
|
+
graphql: resolveField(
|
|
176
|
+
~envKey="REVENTLESS_GRAPHQL_ENDPOINT",
|
|
177
|
+
~fromSource=fromCfg("apiEndpoint"),
|
|
178
|
+
~human="apiEndpoint",
|
|
179
|
+
),
|
|
180
|
+
cognitoRegion: resolveField(
|
|
181
|
+
~envKey="AWS_REGION",
|
|
182
|
+
~fromSource=fromCfg("region"),
|
|
183
|
+
~human="region",
|
|
184
|
+
),
|
|
185
|
+
cognitoClientId: resolveField(
|
|
186
|
+
~envKey="COGNITO_CLIENT_ID",
|
|
187
|
+
~fromSource=fromCfg("cognitoClientId"),
|
|
188
|
+
~human="cognitoClientId",
|
|
189
|
+
),
|
|
190
|
+
}
|
|
191
|
+
| StackOutputs(outputs) =>
|
|
192
|
+
let out = key => outputs->field(key)->Option.flatMap(asString)
|
|
193
|
+
{
|
|
194
|
+
graphql: switch (
|
|
195
|
+
Seed.Prompt.envValue("REVENTLESS_GRAPHQL_ENDPOINT"),
|
|
196
|
+
out("domainMergedApiEndpoint"),
|
|
197
|
+
out("domainApiEndpoint"),
|
|
198
|
+
) {
|
|
199
|
+
| (Some(v), _, _) | (_, Some(v), _) | (_, _, Some(v)) => v
|
|
200
|
+
| _ =>
|
|
201
|
+
throw(
|
|
202
|
+
Seed.Failed(
|
|
203
|
+
`stack "${stack}" exports neither domainMergedApiEndpoint nor domainApiEndpoint`,
|
|
204
|
+
),
|
|
205
|
+
)
|
|
206
|
+
},
|
|
207
|
+
cognitoRegion: resolveField(
|
|
208
|
+
~envKey="AWS_REGION",
|
|
209
|
+
~fromSource=out("cognitoRegion"),
|
|
210
|
+
~human="cognitoRegion",
|
|
211
|
+
),
|
|
212
|
+
cognitoClientId: resolveField(
|
|
213
|
+
~envKey="COGNITO_CLIENT_ID",
|
|
214
|
+
~fromSource=out("cognitoUserPoolClientId"),
|
|
215
|
+
~human="cognitoUserPoolClientId",
|
|
216
|
+
),
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let resolveEndpoints = async (
|
|
221
|
+
~projectDir: string,
|
|
222
|
+
~backend: option<string>,
|
|
223
|
+
~stack: string,
|
|
224
|
+
): endpoints => {
|
|
225
|
+
let outputs = stackOutputs(~projectDir, ~backend, stack)
|
|
226
|
+
let source = switch outputs->field("hostShellUrl")->Option.flatMap(asString) {
|
|
227
|
+
| Some(hostShellUrl) => HostShellConfig(await fetchConfig(hostShellUrl))
|
|
228
|
+
| None => StackOutputs(outputs)
|
|
229
|
+
}
|
|
230
|
+
endpointsFrom(~stack, source)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ── Cognito login ─────────────────────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
let cognito = (~region: string, ~clientId: string) => async (
|
|
236
|
+
~username: string,
|
|
237
|
+
~password: string,
|
|
238
|
+
): string => {
|
|
239
|
+
let body = JSON.stringify(
|
|
240
|
+
JSON.Encode.object(
|
|
241
|
+
Dict.fromArray([
|
|
242
|
+
("AuthFlow", JSON.Encode.string("USER_PASSWORD_AUTH")),
|
|
243
|
+
("ClientId", JSON.Encode.string(clientId)),
|
|
244
|
+
(
|
|
245
|
+
"AuthParameters",
|
|
246
|
+
JSON.Encode.object(
|
|
247
|
+
Dict.fromArray([
|
|
248
|
+
("USERNAME", JSON.Encode.string(username)),
|
|
249
|
+
("PASSWORD", JSON.Encode.string(password)),
|
|
250
|
+
]),
|
|
251
|
+
),
|
|
252
|
+
),
|
|
253
|
+
]),
|
|
254
|
+
),
|
|
255
|
+
)
|
|
256
|
+
let headers = Dict.fromArray([
|
|
257
|
+
("content-type", "application/x-amz-json-1.1"),
|
|
258
|
+
("x-amz-target", "AWSCognitoIdentityProviderService.InitiateAuth"),
|
|
259
|
+
])
|
|
260
|
+
let res = try await fetch(`https://cognito-idp.${region}.amazonaws.com/`, {
|
|
261
|
+
method: "POST",
|
|
262
|
+
headers,
|
|
263
|
+
body,
|
|
264
|
+
}) catch {
|
|
265
|
+
| _ => throw(Seed.Failed(`cannot reach Cognito in ${region}`))
|
|
266
|
+
}
|
|
267
|
+
let json = await res->responseJson
|
|
268
|
+
if !(res->responseOk) {
|
|
269
|
+
let detail =
|
|
270
|
+
json->field("message")->Option.flatMap(asString)->Option.getOr(JSON.stringify(json))
|
|
271
|
+
throw(
|
|
272
|
+
Seed.Failed(
|
|
273
|
+
`Cognito InitiateAuth failed (HTTP ${(res->responseStatus)->Int.toString}): ${detail}`,
|
|
274
|
+
),
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
switch json->field("ChallengeName")->Option.flatMap(asString) {
|
|
278
|
+
| Some(challenge) =>
|
|
279
|
+
throw(
|
|
280
|
+
Seed.Failed(
|
|
281
|
+
`Cognito returned challenge ${challenge} — set a permanent password first ` ++
|
|
282
|
+
"(aws cognito-idp admin-set-user-password … --permanent).",
|
|
283
|
+
),
|
|
284
|
+
)
|
|
285
|
+
| None => ()
|
|
286
|
+
}
|
|
287
|
+
switch json
|
|
288
|
+
->field("AuthenticationResult")
|
|
289
|
+
->Option.flatMap(ar => ar->field("IdToken"))
|
|
290
|
+
->Option.flatMap(asString) {
|
|
291
|
+
| Some(token) => token
|
|
292
|
+
| None => throw(Seed.Failed(`Cognito response carried no IdToken: ${JSON.stringify(json)}`))
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ── connect ───────────────────────────────────────────────────────────────────
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* A `connect` thunk for a deployed AWS stack, ready to pass to `Seed.Runner.seed`.
|
|
300
|
+
*
|
|
301
|
+
* `projectDir` is where the Pulumi stacks live (relative to the seed's cwd);
|
|
302
|
+
* `stack` fixes the stack, else `SEED_STACK` or a menu chooses it. Resolves the
|
|
303
|
+
* endpoints, then prompts credentials and mints a Cognito id token.
|
|
304
|
+
*
|
|
305
|
+
* `backend` pins the Pulumi backend the stack lives in — Pulumi Cloud
|
|
306
|
+
* (`https://api.pulumi.com`, needs `PULUMI_ACCESS_TOKEN`) for a CI-deployed
|
|
307
|
+
* example, or a self-hosted store (`s3://…?region=…`, needs AWS creds) for a
|
|
308
|
+
* self-hosted app. It is passed to the pulumi subprocess as `PULUMI_BACKEND_URL`
|
|
309
|
+
* on a copy of the environment, so the operator's persistent `pulumi login` is
|
|
310
|
+
* untouched and each example seeds its own backend regardless of the current
|
|
311
|
+
* login. `SEED_PULUMI_BACKEND` overrides it; omit both to use the ambient login.
|
|
312
|
+
*/
|
|
313
|
+
let connect = (~projectDir: string=".", ~stack=?, ~backend=?, ()): (
|
|
314
|
+
unit => promise<Seed.connection>
|
|
315
|
+
) =>
|
|
316
|
+
async () => {
|
|
317
|
+
let backend = switch Seed.Prompt.envValue("SEED_PULUMI_BACKEND") {
|
|
318
|
+
| Some(url) => Some(url)
|
|
319
|
+
| None => backend
|
|
320
|
+
}
|
|
321
|
+
let stackName = await resolveStack(~projectDir, ~backend, ~stack)
|
|
322
|
+
let eps = await resolveEndpoints(~projectDir, ~backend, ~stack=stackName)
|
|
323
|
+
await Seed.Connect.make(
|
|
324
|
+
~label=stackName,
|
|
325
|
+
~endpoint=eps.graphql,
|
|
326
|
+
~login=cognito(~region=eps.cognitoRegion, ~clientId=eps.cognitoClientId),
|
|
327
|
+
)
|
|
328
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|