@oondemand/oon-core-back 0.3.21 → 0.3.23
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/package.json +1 -1
- package/src/delivery/mongoProvisioning.js +394 -0
- package/src/delivery/runtime.js +60 -29
package/package.json
CHANGED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { mongo } = require("mongoose");
|
|
4
|
+
|
|
5
|
+
const DIGITALOCEAN_API = "https://api.digitalocean.com/v2";
|
|
6
|
+
const DIGITALOCEAN_HOST_SUFFIX = ".mongo.ondigitalocean.com";
|
|
7
|
+
|
|
8
|
+
function randomSecret(bytes = 32) {
|
|
9
|
+
return require("node:crypto").randomBytes(bytes).toString("base64url");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function dnsLabel(value, max = 63) {
|
|
13
|
+
return String(value || "")
|
|
14
|
+
.toLowerCase()
|
|
15
|
+
.replace(/[^a-z0-9-]+/g, "-")
|
|
16
|
+
.replace(/^-+|-+$/g, "")
|
|
17
|
+
.slice(0, max)
|
|
18
|
+
.replace(/-+$/, "");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function mongoIdentity(appCode, environment) {
|
|
22
|
+
return {
|
|
23
|
+
dbName: dnsLabel(`${appCode}_${environment}`, 48).replace(/-/g, "_"),
|
|
24
|
+
username: dnsLabel(`${appCode}-${environment}`, 48),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function mongoHost(uri) {
|
|
29
|
+
try {
|
|
30
|
+
return new URL(uri).hostname.toLowerCase();
|
|
31
|
+
} catch {
|
|
32
|
+
throw new Error("MONGO_ADMIN_URI inválida.");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function resolveMongoProvisioner({ adminUri, provisioner = "auto" }) {
|
|
37
|
+
const normalized = String(provisioner || "auto").trim().toLowerCase();
|
|
38
|
+
if (!["auto", "native", "digitalocean"].includes(normalized)) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`MONGO_PROVISIONER inválido: ${normalized}. Use auto, native ou digitalocean.`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
if (normalized !== "auto") return normalized;
|
|
44
|
+
return mongoHost(adminUri).endsWith(DIGITALOCEAN_HOST_SUFFIX)
|
|
45
|
+
? "digitalocean"
|
|
46
|
+
: "native";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function buildRuntimeMongoUri({
|
|
50
|
+
adminUri,
|
|
51
|
+
username,
|
|
52
|
+
password,
|
|
53
|
+
dbName,
|
|
54
|
+
authSource,
|
|
55
|
+
}) {
|
|
56
|
+
const uri = new URL(adminUri);
|
|
57
|
+
uri.username = username;
|
|
58
|
+
uri.password = password;
|
|
59
|
+
uri.pathname = `/${dbName}`;
|
|
60
|
+
uri.searchParams.set("authSource", authSource);
|
|
61
|
+
return uri.toString();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function validateMongoUri(
|
|
65
|
+
mongoUri,
|
|
66
|
+
{
|
|
67
|
+
MongoClient = mongo.MongoClient,
|
|
68
|
+
timeoutMs = 15000,
|
|
69
|
+
writeProbe = true,
|
|
70
|
+
} = {},
|
|
71
|
+
) {
|
|
72
|
+
const client = new MongoClient(mongoUri, {
|
|
73
|
+
serverSelectionTimeoutMS: timeoutMs,
|
|
74
|
+
connectTimeoutMS: timeoutMs,
|
|
75
|
+
maxPoolSize: 2,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
await client.connect();
|
|
79
|
+
try {
|
|
80
|
+
const db = client.db();
|
|
81
|
+
await db.command({ ping: 1 });
|
|
82
|
+
if (writeProbe) {
|
|
83
|
+
const probe = db.collection("__oon_runtime_probe");
|
|
84
|
+
const id = "provisioning";
|
|
85
|
+
await probe.updateOne(
|
|
86
|
+
{ _id: id },
|
|
87
|
+
{ $set: { checkedAt: new Date() } },
|
|
88
|
+
{ upsert: true },
|
|
89
|
+
);
|
|
90
|
+
await probe.deleteOne({ _id: id });
|
|
91
|
+
}
|
|
92
|
+
} finally {
|
|
93
|
+
await client.close();
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function waitForMongoUri(
|
|
99
|
+
mongoUri,
|
|
100
|
+
{
|
|
101
|
+
validate = validateMongoUri,
|
|
102
|
+
attempts = 12,
|
|
103
|
+
delayMs = 5000,
|
|
104
|
+
} = {},
|
|
105
|
+
) {
|
|
106
|
+
let lastError;
|
|
107
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
108
|
+
try {
|
|
109
|
+
await validate(mongoUri);
|
|
110
|
+
return true;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
lastError = error;
|
|
113
|
+
if (attempt < attempts) {
|
|
114
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const reason = lastError?.codeName || lastError?.code || lastError?.name;
|
|
119
|
+
throw new Error(
|
|
120
|
+
`A credencial derivada do MongoDB não passou no teste de leitura e escrita${
|
|
121
|
+
reason ? ` (${reason})` : ""
|
|
122
|
+
}.`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function provisionNativeMongo({ adminUri, appCode, environment }) {
|
|
127
|
+
const { dbName, username } = mongoIdentity(appCode, environment);
|
|
128
|
+
const password = randomSecret(30);
|
|
129
|
+
const client = new mongo.MongoClient(adminUri, {
|
|
130
|
+
serverSelectionTimeoutMS: 15000,
|
|
131
|
+
});
|
|
132
|
+
await client.connect();
|
|
133
|
+
try {
|
|
134
|
+
const db = client.db(dbName);
|
|
135
|
+
try {
|
|
136
|
+
await db.command({
|
|
137
|
+
createUser: username,
|
|
138
|
+
pwd: password,
|
|
139
|
+
roles: [{ role: "readWrite", db: dbName }],
|
|
140
|
+
});
|
|
141
|
+
} catch (error) {
|
|
142
|
+
const alreadyExists =
|
|
143
|
+
error.codeName === "DuplicateKey" ||
|
|
144
|
+
error.code === 51003 ||
|
|
145
|
+
/already exists/i.test(error.message);
|
|
146
|
+
if (!alreadyExists) throw error;
|
|
147
|
+
await db.command({
|
|
148
|
+
updateUser: username,
|
|
149
|
+
pwd: password,
|
|
150
|
+
roles: [{ role: "readWrite", db: dbName }],
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
} finally {
|
|
154
|
+
await client.close();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
dbName,
|
|
159
|
+
username,
|
|
160
|
+
mongoUri: buildRuntimeMongoUri({
|
|
161
|
+
adminUri,
|
|
162
|
+
username,
|
|
163
|
+
password,
|
|
164
|
+
dbName,
|
|
165
|
+
authSource: dbName,
|
|
166
|
+
}),
|
|
167
|
+
provider: "native",
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function digitalOceanRequest(
|
|
172
|
+
{
|
|
173
|
+
token,
|
|
174
|
+
clusterId,
|
|
175
|
+
path,
|
|
176
|
+
method = "GET",
|
|
177
|
+
body,
|
|
178
|
+
expected = [200],
|
|
179
|
+
},
|
|
180
|
+
{ fetchImpl = globalThis.fetch } = {},
|
|
181
|
+
) {
|
|
182
|
+
if (!fetchImpl) throw new Error("fetch não disponível para a DigitalOcean API.");
|
|
183
|
+
const controller = new AbortController();
|
|
184
|
+
const timeout = setTimeout(() => controller.abort(), 20000);
|
|
185
|
+
let response;
|
|
186
|
+
try {
|
|
187
|
+
response = await fetchImpl(
|
|
188
|
+
`${DIGITALOCEAN_API}/databases/${encodeURIComponent(clusterId)}${path}`,
|
|
189
|
+
{
|
|
190
|
+
method,
|
|
191
|
+
headers: {
|
|
192
|
+
Authorization: `Bearer ${token}`,
|
|
193
|
+
Accept: "application/json",
|
|
194
|
+
...(body ? { "Content-Type": "application/json" } : {}),
|
|
195
|
+
},
|
|
196
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
197
|
+
signal: controller.signal,
|
|
198
|
+
},
|
|
199
|
+
);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
if (error.name === "AbortError") {
|
|
202
|
+
throw new Error("Timeout ao acessar a DigitalOcean API.");
|
|
203
|
+
}
|
|
204
|
+
throw new Error(`Falha ao acessar a DigitalOcean API: ${error.message}`);
|
|
205
|
+
} finally {
|
|
206
|
+
clearTimeout(timeout);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const payload = await response.json().catch(() => ({}));
|
|
210
|
+
if (!expected.includes(response.status)) {
|
|
211
|
+
const error = new Error(
|
|
212
|
+
`DigitalOcean API rejeitou ${method} ${path}: HTTP ${response.status} (${payload.id || "erro"}).`,
|
|
213
|
+
);
|
|
214
|
+
error.status = response.status;
|
|
215
|
+
error.payload = payload;
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
return { status: response.status, payload };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function ensureDigitalOceanDatabase(options, deps) {
|
|
222
|
+
const path = `/dbs/${encodeURIComponent(options.dbName)}`;
|
|
223
|
+
try {
|
|
224
|
+
await digitalOceanRequest(
|
|
225
|
+
{ ...options, path, expected: [200] },
|
|
226
|
+
deps,
|
|
227
|
+
);
|
|
228
|
+
return;
|
|
229
|
+
} catch (error) {
|
|
230
|
+
if (error.status !== 404) throw error;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
await digitalOceanRequest(
|
|
234
|
+
{
|
|
235
|
+
...options,
|
|
236
|
+
path: "/dbs",
|
|
237
|
+
method: "POST",
|
|
238
|
+
body: { name: options.dbName },
|
|
239
|
+
expected: [201, 202],
|
|
240
|
+
},
|
|
241
|
+
deps,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function digitalOceanUserSettings(dbName) {
|
|
246
|
+
return {
|
|
247
|
+
mongo_user_settings: {
|
|
248
|
+
databases: [dbName],
|
|
249
|
+
role: "readWrite",
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function provisionDigitalOceanUser(options, deps) {
|
|
255
|
+
const userPath = `/users/${encodeURIComponent(options.username)}`;
|
|
256
|
+
let exists = true;
|
|
257
|
+
try {
|
|
258
|
+
await digitalOceanRequest(
|
|
259
|
+
{ ...options, path: userPath, expected: [200] },
|
|
260
|
+
deps,
|
|
261
|
+
);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
if (error.status !== 404) throw error;
|
|
264
|
+
exists = false;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const settings = digitalOceanUserSettings(options.dbName);
|
|
268
|
+
let result;
|
|
269
|
+
if (!exists) {
|
|
270
|
+
result = await digitalOceanRequest(
|
|
271
|
+
{
|
|
272
|
+
...options,
|
|
273
|
+
path: "/users",
|
|
274
|
+
method: "POST",
|
|
275
|
+
body: { name: options.username, settings },
|
|
276
|
+
expected: [201, 202],
|
|
277
|
+
},
|
|
278
|
+
deps,
|
|
279
|
+
);
|
|
280
|
+
} else {
|
|
281
|
+
await digitalOceanRequest(
|
|
282
|
+
{
|
|
283
|
+
...options,
|
|
284
|
+
path: userPath,
|
|
285
|
+
method: "PUT",
|
|
286
|
+
body: { settings },
|
|
287
|
+
expected: [200, 202],
|
|
288
|
+
},
|
|
289
|
+
deps,
|
|
290
|
+
);
|
|
291
|
+
result = await digitalOceanRequest(
|
|
292
|
+
{
|
|
293
|
+
...options,
|
|
294
|
+
path: `${userPath}/reset_auth`,
|
|
295
|
+
method: "POST",
|
|
296
|
+
body: {},
|
|
297
|
+
expected: [200, 201, 202],
|
|
298
|
+
},
|
|
299
|
+
deps,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const user = result.payload.user;
|
|
304
|
+
if (!user?.password) {
|
|
305
|
+
throw new Error(
|
|
306
|
+
"DigitalOcean API não retornou a senha do usuário. O token precisa do escopo database:view_credentials.",
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
return user;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function provisionDigitalOceanMongo(
|
|
313
|
+
{
|
|
314
|
+
adminUri,
|
|
315
|
+
appCode,
|
|
316
|
+
environment,
|
|
317
|
+
digitalOceanToken,
|
|
318
|
+
digitalOceanClusterId,
|
|
319
|
+
},
|
|
320
|
+
deps = {},
|
|
321
|
+
) {
|
|
322
|
+
if (!digitalOceanToken) {
|
|
323
|
+
throw new Error(
|
|
324
|
+
"DIGITALOCEAN_TOKEN é obrigatório para provisionar usuários no MongoDB gerenciado da DigitalOcean.",
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
if (!digitalOceanClusterId) {
|
|
328
|
+
throw new Error(
|
|
329
|
+
"DIGITALOCEAN_DATABASE_CLUSTER_ID é obrigatório para provisionar usuários no MongoDB gerenciado da DigitalOcean.",
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const { dbName, username } = mongoIdentity(appCode, environment);
|
|
334
|
+
const apiOptions = {
|
|
335
|
+
token: digitalOceanToken,
|
|
336
|
+
clusterId: digitalOceanClusterId,
|
|
337
|
+
dbName,
|
|
338
|
+
username,
|
|
339
|
+
};
|
|
340
|
+
await ensureDigitalOceanDatabase(apiOptions, deps);
|
|
341
|
+
const user = await provisionDigitalOceanUser(apiOptions, deps);
|
|
342
|
+
const mongoUri = buildRuntimeMongoUri({
|
|
343
|
+
adminUri,
|
|
344
|
+
username: user.name || username,
|
|
345
|
+
password: user.password,
|
|
346
|
+
dbName,
|
|
347
|
+
authSource: "admin",
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
await waitForMongoUri(mongoUri, {
|
|
351
|
+
validate: deps.validateMongoUri || validateMongoUri,
|
|
352
|
+
attempts: deps.validationAttempts,
|
|
353
|
+
delayMs: deps.validationDelayMs,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
dbName,
|
|
358
|
+
username: user.name || username,
|
|
359
|
+
mongoUri,
|
|
360
|
+
provider: "digitalocean",
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function provisionMongo(options, deps = {}) {
|
|
365
|
+
const provider = resolveMongoProvisioner(options);
|
|
366
|
+
if (provider === "digitalocean") {
|
|
367
|
+
return provisionDigitalOceanMongo(options, deps);
|
|
368
|
+
}
|
|
369
|
+
const result = await provisionNativeMongo(options);
|
|
370
|
+
await waitForMongoUri(result.mongoUri, {
|
|
371
|
+
validate: deps.validateMongoUri || validateMongoUri,
|
|
372
|
+
attempts: deps.validationAttempts,
|
|
373
|
+
delayMs: deps.validationDelayMs,
|
|
374
|
+
});
|
|
375
|
+
return result;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
module.exports = {
|
|
379
|
+
DIGITALOCEAN_API,
|
|
380
|
+
DIGITALOCEAN_HOST_SUFFIX,
|
|
381
|
+
mongoIdentity,
|
|
382
|
+
mongoHost,
|
|
383
|
+
resolveMongoProvisioner,
|
|
384
|
+
buildRuntimeMongoUri,
|
|
385
|
+
validateMongoUri,
|
|
386
|
+
waitForMongoUri,
|
|
387
|
+
provisionNativeMongo,
|
|
388
|
+
digitalOceanRequest,
|
|
389
|
+
ensureDigitalOceanDatabase,
|
|
390
|
+
digitalOceanUserSettings,
|
|
391
|
+
provisionDigitalOceanUser,
|
|
392
|
+
provisionDigitalOceanMongo,
|
|
393
|
+
provisionMongo,
|
|
394
|
+
};
|
package/src/delivery/runtime.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const crypto = require("node:crypto");
|
|
4
|
-
const { mongo } = require("mongoose");
|
|
5
4
|
const { spawnSync } = require("node:child_process");
|
|
6
5
|
const { kubectl } = require("./command");
|
|
7
6
|
const { validateInfraFromCluster } = require("../deploy/contracts/infra-contract");
|
|
7
|
+
const { provisionMongo, validateMongoUri } = require("./mongoProvisioning");
|
|
8
8
|
|
|
9
9
|
const RESOURCE_PROFILES = {
|
|
10
10
|
nano: { cpuRequest: "50m", cpuLimit: "250m", memoryRequest: "96Mi", memoryLimit: "256Mi" },
|
|
@@ -28,28 +28,6 @@ function readSecret(namespace, name, key) {
|
|
|
28
28
|
} catch { return null; }
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
async function provisionMongo({ adminUri, appCode, environment }) {
|
|
32
|
-
const dbName = dnsLabel(`${appCode}_${environment}`, 48).replace(/-/g, "_");
|
|
33
|
-
const username = dnsLabel(`${appCode}-${environment}`, 48);
|
|
34
|
-
const password = randomSecret(30);
|
|
35
|
-
const client = new mongo.MongoClient(adminUri, { serverSelectionTimeoutMS: 15000 });
|
|
36
|
-
await client.connect();
|
|
37
|
-
try {
|
|
38
|
-
const db = client.db(dbName);
|
|
39
|
-
try { await db.command({ createUser: username, pwd: password, roles: [{ role: "readWrite", db: dbName }] }); }
|
|
40
|
-
catch (error) {
|
|
41
|
-
if (error.codeName !== "DuplicateKey" && error.code !== 51003 && !/already exists/i.test(error.message)) throw error;
|
|
42
|
-
await db.command({ updateUser: username, pwd: password, roles: [{ role: "readWrite", db: dbName }] });
|
|
43
|
-
}
|
|
44
|
-
} finally { await client.close(); }
|
|
45
|
-
const uri = new URL(adminUri);
|
|
46
|
-
uri.username = username;
|
|
47
|
-
uri.password = password;
|
|
48
|
-
uri.pathname = `/${dbName}`;
|
|
49
|
-
uri.searchParams.set("authSource", dbName);
|
|
50
|
-
return { dbName, mongoUri: uri.toString() };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
31
|
function baselineManifest({ context }) {
|
|
54
32
|
return `apiVersion: v1
|
|
55
33
|
kind: Namespace
|
|
@@ -189,18 +167,71 @@ function applyRuntimeSecret({ context, mongoUri, encryptionKey, extraSecrets = {
|
|
|
189
167
|
async function provisionRuntime({ context, extraSecrets = {} }) {
|
|
190
168
|
const infra = validateInfraFromCluster().config;
|
|
191
169
|
applyYaml(baselineManifest({ context, infra }));
|
|
170
|
+
|
|
171
|
+
const platformNamespace = infra.PLATFORM_NAMESPACE || "oon-system";
|
|
172
|
+
const adminSecretName = infra.MONGO_ADMIN_SECRET_NAME || "oon-mongo-admin";
|
|
173
|
+
const adminUri = readSecret(
|
|
174
|
+
platformNamespace,
|
|
175
|
+
adminSecretName,
|
|
176
|
+
"MONGO_ADMIN_URI",
|
|
177
|
+
);
|
|
178
|
+
if (!adminUri) {
|
|
179
|
+
throw new Error("MongoDB administrativo não configurado na plataforma.");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const mongoProvisioner =
|
|
183
|
+
readSecret(platformNamespace, adminSecretName, "MONGO_PROVISIONER") ||
|
|
184
|
+
"auto";
|
|
185
|
+
const digitalOceanToken = readSecret(
|
|
186
|
+
platformNamespace,
|
|
187
|
+
adminSecretName,
|
|
188
|
+
"DIGITALOCEAN_TOKEN",
|
|
189
|
+
);
|
|
190
|
+
const digitalOceanClusterId = readSecret(
|
|
191
|
+
platformNamespace,
|
|
192
|
+
adminSecretName,
|
|
193
|
+
"DIGITALOCEAN_DATABASE_CLUSTER_ID",
|
|
194
|
+
);
|
|
195
|
+
|
|
192
196
|
let mongoUri = readSecret(context.namespace, "oon-runtime", "MONGO_URI");
|
|
193
197
|
let databaseName = "";
|
|
198
|
+
let credentialRotated = false;
|
|
199
|
+
|
|
200
|
+
if (mongoUri) {
|
|
201
|
+
try {
|
|
202
|
+
await validateMongoUri(mongoUri);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.warn(
|
|
205
|
+
"A credencial MongoDB persistida não passou na validação; " +
|
|
206
|
+
"ela será reprovisionada.",
|
|
207
|
+
);
|
|
208
|
+
mongoUri = null;
|
|
209
|
+
credentialRotated = true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
194
213
|
if (!mongoUri) {
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
214
|
+
const provisioned = await provisionMongo({
|
|
215
|
+
adminUri,
|
|
216
|
+
appCode: context.appCode,
|
|
217
|
+
environment: context.environment,
|
|
218
|
+
provisioner: mongoProvisioner,
|
|
219
|
+
digitalOceanToken,
|
|
220
|
+
digitalOceanClusterId,
|
|
221
|
+
});
|
|
198
222
|
mongoUri = provisioned.mongoUri;
|
|
199
223
|
databaseName = provisioned.dbName;
|
|
224
|
+
credentialRotated = true;
|
|
200
225
|
}
|
|
201
|
-
|
|
226
|
+
|
|
227
|
+
const encryptionKey =
|
|
228
|
+
readSecret(
|
|
229
|
+
context.namespace,
|
|
230
|
+
"oon-runtime",
|
|
231
|
+
"INSTANCE_CREDENTIAL_ENCRYPTION_KEY",
|
|
232
|
+
) || randomSecret(48);
|
|
202
233
|
applyRuntimeSecret({ context, mongoUri, encryptionKey, extraSecrets });
|
|
203
|
-
return { infra, databaseName };
|
|
234
|
+
return { infra, databaseName, credentialRotated };
|
|
204
235
|
}
|
|
205
236
|
|
|
206
237
|
function deployRuntime({ context, infra, imageDigest, resourceProfile = "small" }) {
|
|
@@ -249,4 +280,4 @@ async function activateMember(context) {
|
|
|
249
280
|
return data;
|
|
250
281
|
}
|
|
251
282
|
|
|
252
|
-
module.exports = { RESOURCE_PROFILES, randomSecret, dnsLabel, provisionMongo, baselineManifest, runtimeManifest, provisionRuntime, deployRuntime, waitHttp, activateMember };
|
|
283
|
+
module.exports = { RESOURCE_PROFILES, randomSecret, dnsLabel, provisionMongo, validateMongoUri, baselineManifest, runtimeManifest, provisionRuntime, deployRuntime, waitHttp, activateMember };
|