relight-cli 0.2.0 → 0.3.0
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/cli.js +94 -49
- package/src/commands/apps.js +10 -10
- package/src/commands/auth.js +0 -62
- package/src/commands/config.js +16 -16
- package/src/commands/cost.js +6 -6
- package/src/commands/db.js +434 -190
- package/src/commands/deploy.js +38 -72
- package/src/commands/doctor.js +28 -4
- package/src/commands/domains.js +22 -22
- package/src/commands/logs.js +4 -4
- package/src/commands/open.js +4 -4
- package/src/commands/ps.js +4 -4
- package/src/commands/scale.js +4 -4
- package/src/commands/service.js +227 -0
- package/src/lib/clouds/gcp.js +21 -1
- package/src/lib/clouds/neon.js +147 -0
- package/src/lib/config.js +169 -11
- package/src/lib/link.js +13 -2
- package/src/lib/providers/aws/db.js +217 -226
- package/src/lib/providers/cf/db.js +33 -131
- package/src/lib/providers/gcp/db.js +169 -254
- package/src/lib/providers/neon/db.js +306 -0
- package/src/lib/providers/resolve.js +33 -3
|
@@ -5,32 +5,26 @@ import {
|
|
|
5
5
|
getSqlInstance,
|
|
6
6
|
deleteSqlInstance,
|
|
7
7
|
createSqlDatabase,
|
|
8
|
+
deleteSqlDatabase,
|
|
8
9
|
createSqlUser,
|
|
9
10
|
updateSqlUser,
|
|
11
|
+
deleteSqlUser,
|
|
12
|
+
listSqlDatabases,
|
|
10
13
|
} from "../../clouds/gcp.js";
|
|
11
|
-
import {
|
|
14
|
+
import { getCloudMeta, setCloudMeta } from "../../config.js";
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
return `relight-${appName}`;
|
|
15
|
-
}
|
|
16
|
+
var SHARED_INSTANCE = "relight-shared";
|
|
16
17
|
|
|
17
|
-
function
|
|
18
|
-
return `
|
|
18
|
+
function userName(name) {
|
|
19
|
+
return `app_${name.replace(/-/g, "_")}`;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// Read from Cloud Run service env vars
|
|
25
|
-
var { listAllServices } = await import("../../clouds/gcp.js");
|
|
26
|
-
var all = await listAllServices(token, cfg.project);
|
|
27
|
-
var svc = all.find((s) => s.name.split("/").pop() === `relight-${appName}`);
|
|
28
|
-
if (!svc) throw new Error(`Service relight-${appName} not found.`);
|
|
22
|
+
function dbName(name) {
|
|
23
|
+
return `relight_${name.replace(/-/g, "_")}`;
|
|
24
|
+
}
|
|
29
25
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (!dbToken) throw new Error("DB_TOKEN not found on service.");
|
|
33
|
-
return dbToken.value;
|
|
26
|
+
function isSharedInstance(dbId) {
|
|
27
|
+
return dbId === SHARED_INSTANCE;
|
|
34
28
|
}
|
|
35
29
|
|
|
36
30
|
async function connectPg(connectionUrl) {
|
|
@@ -41,27 +35,37 @@ async function connectPg(connectionUrl) {
|
|
|
41
35
|
return client;
|
|
42
36
|
}
|
|
43
37
|
|
|
44
|
-
|
|
45
|
-
var
|
|
46
|
-
|
|
47
|
-
if (!opts.skipAppConfig) {
|
|
48
|
-
var appConfig = await getAppConfig(cfg, appName);
|
|
49
|
-
if (!appConfig) {
|
|
50
|
-
throw new Error(`App ${appName} not found.`);
|
|
51
|
-
}
|
|
52
|
-
if (appConfig.dbId) {
|
|
53
|
-
throw new Error(`App ${appName} already has a database: ${appConfig.dbId}`);
|
|
54
|
-
}
|
|
55
|
-
region = appConfig.regions?.[0] || "us-central1";
|
|
38
|
+
function getPublicIp(instance) {
|
|
39
|
+
for (var addr of (instance.ipAddresses || [])) {
|
|
40
|
+
if (addr.type === "PRIMARY") return addr.ipAddress;
|
|
56
41
|
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
57
44
|
|
|
45
|
+
async function getOrCreateSharedInstance(cfg, region) {
|
|
58
46
|
var token = await mintAccessToken(cfg.clientEmail, cfg.privateKey);
|
|
59
|
-
var
|
|
47
|
+
var meta = getCloudMeta("gcp", "sharedDb");
|
|
48
|
+
|
|
49
|
+
if (meta && meta.instance) {
|
|
50
|
+
// Verify instance still exists
|
|
51
|
+
try {
|
|
52
|
+
var instance = await getSqlInstance(token, cfg.project, SHARED_INSTANCE);
|
|
53
|
+
var ip = getPublicIp(instance);
|
|
54
|
+
if (ip && ip !== meta.ip) {
|
|
55
|
+
meta.ip = ip;
|
|
56
|
+
setCloudMeta("gcp", "sharedDb", meta);
|
|
57
|
+
}
|
|
58
|
+
return { token, ip: ip || meta.ip, meta };
|
|
59
|
+
} catch (e) {
|
|
60
|
+
// Instance gone, recreate
|
|
61
|
+
}
|
|
62
|
+
}
|
|
60
63
|
|
|
61
|
-
// Create
|
|
64
|
+
// Create shared instance
|
|
65
|
+
process.stderr.write(" Creating shared Cloud SQL instance (one-time, takes 5-15 minutes)...\n");
|
|
62
66
|
await createSqlInstance(token, cfg.project, {
|
|
63
|
-
name:
|
|
64
|
-
region,
|
|
67
|
+
name: SHARED_INSTANCE,
|
|
68
|
+
region: region || "us-central1",
|
|
65
69
|
databaseVersion: "POSTGRES_15",
|
|
66
70
|
settings: {
|
|
67
71
|
tier: "db-f1-micro",
|
|
@@ -75,115 +79,138 @@ export async function createDatabase(cfg, appName, opts = {}) {
|
|
|
75
79
|
},
|
|
76
80
|
});
|
|
77
81
|
|
|
78
|
-
// Create
|
|
79
|
-
var
|
|
80
|
-
await
|
|
82
|
+
// Create master user with random password
|
|
83
|
+
var masterPassword = randomBytes(24).toString("base64url");
|
|
84
|
+
await createSqlUser(token, cfg.project, SHARED_INSTANCE, "relight_admin", masterPassword);
|
|
81
85
|
|
|
82
|
-
|
|
83
|
-
var
|
|
84
|
-
|
|
86
|
+
var instance = await getSqlInstance(token, cfg.project, SHARED_INSTANCE);
|
|
87
|
+
var ip = getPublicIp(instance);
|
|
88
|
+
if (!ip) throw new Error("No public IP assigned to shared Cloud SQL instance.");
|
|
85
89
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
meta = { instance: SHARED_INSTANCE, ip, masterPassword };
|
|
91
|
+
setCloudMeta("gcp", "sharedDb", meta);
|
|
92
|
+
|
|
93
|
+
return { token, ip, meta };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function connectAsAdmin(cfg) {
|
|
97
|
+
var meta = getCloudMeta("gcp", "sharedDb");
|
|
98
|
+
if (!meta || !meta.masterPassword) {
|
|
99
|
+
throw new Error("Shared DB master credentials not found. Run `relight db create` first.");
|
|
94
100
|
}
|
|
95
101
|
|
|
96
|
-
|
|
102
|
+
var token = await mintAccessToken(cfg.clientEmail, cfg.privateKey);
|
|
103
|
+
var instance = await getSqlInstance(token, cfg.project, SHARED_INSTANCE);
|
|
104
|
+
var ip = getPublicIp(instance);
|
|
105
|
+
if (!ip) throw new Error("No public IP on shared instance.");
|
|
106
|
+
|
|
107
|
+
var url = `postgresql://relight_admin:${encodeURIComponent(meta.masterPassword)}@${ip}:5432/postgres`;
|
|
108
|
+
var client = await connectPg(url);
|
|
109
|
+
return { client, ip };
|
|
110
|
+
}
|
|
97
111
|
|
|
98
|
-
|
|
112
|
+
async function destroySharedInstanceIfEmpty(cfg) {
|
|
113
|
+
var { client } = await connectAsAdmin(cfg);
|
|
114
|
+
try {
|
|
115
|
+
var res = await client.query(
|
|
116
|
+
"SELECT datname FROM pg_database WHERE datname LIKE 'relight_%'"
|
|
117
|
+
);
|
|
118
|
+
if (res.rows.length > 0) return false;
|
|
119
|
+
} finally {
|
|
120
|
+
await client.end();
|
|
121
|
+
}
|
|
99
122
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
123
|
+
// No relight databases remain - destroy the shared instance
|
|
124
|
+
var token = await mintAccessToken(cfg.clientEmail, cfg.privateKey);
|
|
125
|
+
await deleteSqlInstance(token, cfg.project, SHARED_INSTANCE);
|
|
126
|
+
setCloudMeta("gcp", "sharedDb", undefined);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
104
129
|
|
|
105
|
-
|
|
106
|
-
if (!appConfig.secretKeys) appConfig.secretKeys = [];
|
|
107
|
-
if (!appConfig.env) appConfig.env = {};
|
|
130
|
+
// --- Public API ---
|
|
108
131
|
|
|
109
|
-
|
|
110
|
-
|
|
132
|
+
export async function createDatabase(cfg, name, opts = {}) {
|
|
133
|
+
var region = opts.location || "us-central1";
|
|
111
134
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
135
|
+
var { token, ip, meta } = await getOrCreateSharedInstance(cfg, region);
|
|
136
|
+
var database = dbName(name);
|
|
137
|
+
var user = userName(name);
|
|
138
|
+
var password = randomBytes(24).toString("base64url");
|
|
116
139
|
|
|
117
|
-
|
|
118
|
-
|
|
140
|
+
// Connect as admin to create database and user
|
|
141
|
+
var adminUrl = `postgresql://relight_admin:${encodeURIComponent(meta.masterPassword)}@${ip}:5432/postgres`;
|
|
142
|
+
var client = await connectPg(adminUrl);
|
|
143
|
+
try {
|
|
144
|
+
await client.query(`CREATE USER ${user} WITH PASSWORD '${password.replace(/'/g, "''")}'`);
|
|
145
|
+
await client.query(`CREATE DATABASE ${database} OWNER ${user}`);
|
|
146
|
+
} finally {
|
|
147
|
+
await client.end();
|
|
119
148
|
}
|
|
120
149
|
|
|
150
|
+
var connectionUrl = `postgresql://${user}:${encodeURIComponent(password)}@${ip}:5432/${database}`;
|
|
151
|
+
|
|
121
152
|
return {
|
|
122
|
-
dbId:
|
|
153
|
+
dbId: SHARED_INSTANCE,
|
|
123
154
|
dbName: database,
|
|
155
|
+
dbUser: user,
|
|
124
156
|
dbToken: password,
|
|
125
157
|
connectionUrl,
|
|
126
158
|
};
|
|
127
159
|
}
|
|
128
160
|
|
|
129
|
-
export async function destroyDatabase(cfg,
|
|
161
|
+
export async function destroyDatabase(cfg, name, opts = {}) {
|
|
130
162
|
var dbId = opts.dbId;
|
|
131
163
|
if (!dbId) {
|
|
132
|
-
|
|
133
|
-
if (!appConfig || !appConfig.dbId) {
|
|
134
|
-
throw new Error(`App ${appName} does not have a database.`);
|
|
135
|
-
}
|
|
136
|
-
dbId = appConfig.dbId;
|
|
164
|
+
throw new Error("dbId is required to destroy a GCP database.");
|
|
137
165
|
}
|
|
138
166
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
167
|
+
// Legacy per-app instance: delete the whole instance
|
|
168
|
+
if (!isSharedInstance(dbId)) {
|
|
169
|
+
var token = await mintAccessToken(cfg.clientEmail, cfg.privateKey);
|
|
170
|
+
await deleteSqlInstance(token, cfg.project, dbId);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
145
173
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
if (appConfig.envKeys) appConfig.envKeys = appConfig.envKeys.filter((k) => k !== "DATABASE_URL");
|
|
151
|
-
if (appConfig.secretKeys) appConfig.secretKeys = appConfig.secretKeys.filter((k) => k !== "DB_TOKEN");
|
|
174
|
+
// Shared instance: drop database and user
|
|
175
|
+
var database = dbName(name);
|
|
176
|
+
var user = userName(name);
|
|
152
177
|
|
|
153
|
-
|
|
178
|
+
var { client } = await connectAsAdmin(cfg);
|
|
179
|
+
try {
|
|
180
|
+
// Terminate active connections to the database
|
|
181
|
+
await client.query(
|
|
182
|
+
`SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${database}' AND pid <> pg_backend_pid()`
|
|
183
|
+
);
|
|
184
|
+
await client.query(`DROP DATABASE IF EXISTS ${database}`);
|
|
185
|
+
await client.query(`DROP USER IF EXISTS ${user}`);
|
|
186
|
+
} finally {
|
|
187
|
+
await client.end();
|
|
154
188
|
}
|
|
189
|
+
|
|
190
|
+
// Check if shared instance should be destroyed
|
|
191
|
+
await destroySharedInstanceIfEmpty(cfg);
|
|
155
192
|
}
|
|
156
193
|
|
|
157
|
-
export async function getDatabaseInfo(cfg,
|
|
194
|
+
export async function getDatabaseInfo(cfg, name, opts = {}) {
|
|
158
195
|
var dbId = opts.dbId;
|
|
159
|
-
var dbNameVal;
|
|
160
196
|
if (!dbId) {
|
|
161
|
-
|
|
162
|
-
if (!appConfig || !appConfig.dbId) {
|
|
163
|
-
throw new Error(`App ${appName} does not have a database.`);
|
|
164
|
-
}
|
|
165
|
-
dbId = appConfig.dbId;
|
|
166
|
-
dbNameVal = appConfig.dbName;
|
|
197
|
+
throw new Error("dbId is required to get GCP database info.");
|
|
167
198
|
}
|
|
168
199
|
|
|
169
200
|
var token = await mintAccessToken(cfg.clientEmail, cfg.privateKey);
|
|
170
201
|
var instance = await getSqlInstance(token, cfg.project, dbId);
|
|
202
|
+
var publicIp = getPublicIp(instance);
|
|
171
203
|
|
|
172
|
-
var
|
|
173
|
-
|
|
174
|
-
if (addr.type === "PRIMARY") {
|
|
175
|
-
publicIp = addr.ipAddress;
|
|
176
|
-
break;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
204
|
+
var displayUser = isSharedInstance(dbId) ? userName(name) : "relight";
|
|
205
|
+
var database = dbName(name);
|
|
179
206
|
|
|
180
207
|
var connectionUrl = publicIp
|
|
181
|
-
? `postgresql
|
|
208
|
+
? `postgresql://${displayUser}:****@${publicIp}:5432/${database}`
|
|
182
209
|
: null;
|
|
183
210
|
|
|
184
211
|
return {
|
|
185
212
|
dbId,
|
|
186
|
-
dbName:
|
|
213
|
+
dbName: database,
|
|
187
214
|
connectionUrl,
|
|
188
215
|
size: null,
|
|
189
216
|
numTables: null,
|
|
@@ -191,34 +218,12 @@ export async function getDatabaseInfo(cfg, appName, opts = {}) {
|
|
|
191
218
|
};
|
|
192
219
|
}
|
|
193
220
|
|
|
194
|
-
export async function queryDatabase(cfg,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (!dbId) {
|
|
198
|
-
var appConfig = await getAppConfig(cfg, appName);
|
|
199
|
-
if (!appConfig || !appConfig.dbId) {
|
|
200
|
-
throw new Error(`App ${appName} does not have a database.`);
|
|
201
|
-
}
|
|
202
|
-
dbId = appConfig.dbId;
|
|
203
|
-
database = appConfig.dbName;
|
|
204
|
-
} else {
|
|
205
|
-
database = dbName(appName);
|
|
221
|
+
export async function queryDatabase(cfg, name, sql, params, opts = {}) {
|
|
222
|
+
if (!opts.connectionUrl) {
|
|
223
|
+
throw new Error("connectionUrl is required to query a GCP database.");
|
|
206
224
|
}
|
|
207
225
|
|
|
208
|
-
var
|
|
209
|
-
var token = await mintAccessToken(cfg.clientEmail, cfg.privateKey);
|
|
210
|
-
var instance = await getSqlInstance(token, cfg.project, dbId);
|
|
211
|
-
|
|
212
|
-
var publicIp = null;
|
|
213
|
-
for (var addr of (instance.ipAddresses || [])) {
|
|
214
|
-
if (addr.type === "PRIMARY") {
|
|
215
|
-
publicIp = addr.ipAddress;
|
|
216
|
-
break;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
var connectionUrl = `postgresql://relight:${encodeURIComponent(password)}@${publicIp}:5432/${database}`;
|
|
221
|
-
var client = await connectPg(connectionUrl);
|
|
226
|
+
var client = await connectPg(opts.connectionUrl);
|
|
222
227
|
|
|
223
228
|
try {
|
|
224
229
|
var result = await client.query(sql, params || []);
|
|
@@ -231,34 +236,12 @@ export async function queryDatabase(cfg, appName, sql, params, opts = {}) {
|
|
|
231
236
|
}
|
|
232
237
|
}
|
|
233
238
|
|
|
234
|
-
export async function importDatabase(cfg,
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (!dbId) {
|
|
238
|
-
var appConfig = await getAppConfig(cfg, appName);
|
|
239
|
-
if (!appConfig || !appConfig.dbId) {
|
|
240
|
-
throw new Error(`App ${appName} does not have a database.`);
|
|
241
|
-
}
|
|
242
|
-
dbId = appConfig.dbId;
|
|
243
|
-
database = appConfig.dbName;
|
|
244
|
-
} else {
|
|
245
|
-
database = dbName(appName);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
var password = await getDbPassword(cfg, appName);
|
|
249
|
-
var token = await mintAccessToken(cfg.clientEmail, cfg.privateKey);
|
|
250
|
-
var instance = await getSqlInstance(token, cfg.project, dbId);
|
|
251
|
-
|
|
252
|
-
var publicIp = null;
|
|
253
|
-
for (var addr of (instance.ipAddresses || [])) {
|
|
254
|
-
if (addr.type === "PRIMARY") {
|
|
255
|
-
publicIp = addr.ipAddress;
|
|
256
|
-
break;
|
|
257
|
-
}
|
|
239
|
+
export async function importDatabase(cfg, name, sqlContent, opts = {}) {
|
|
240
|
+
if (!opts.connectionUrl) {
|
|
241
|
+
throw new Error("connectionUrl is required to import into a GCP database.");
|
|
258
242
|
}
|
|
259
243
|
|
|
260
|
-
var
|
|
261
|
-
var client = await connectPg(connectionUrl);
|
|
244
|
+
var client = await connectPg(opts.connectionUrl);
|
|
262
245
|
|
|
263
246
|
try {
|
|
264
247
|
await client.query(sqlContent);
|
|
@@ -267,34 +250,13 @@ export async function importDatabase(cfg, appName, sqlContent, opts = {}) {
|
|
|
267
250
|
}
|
|
268
251
|
}
|
|
269
252
|
|
|
270
|
-
export async function exportDatabase(cfg,
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if (!dbId) {
|
|
274
|
-
var appConfig = await getAppConfig(cfg, appName);
|
|
275
|
-
if (!appConfig || !appConfig.dbId) {
|
|
276
|
-
throw new Error(`App ${appName} does not have a database.`);
|
|
277
|
-
}
|
|
278
|
-
dbId = appConfig.dbId;
|
|
279
|
-
database = appConfig.dbName;
|
|
280
|
-
} else {
|
|
281
|
-
database = dbName(appName);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
var password = await getDbPassword(cfg, appName);
|
|
285
|
-
var token = await mintAccessToken(cfg.clientEmail, cfg.privateKey);
|
|
286
|
-
var instance = await getSqlInstance(token, cfg.project, dbId);
|
|
287
|
-
|
|
288
|
-
var publicIp = null;
|
|
289
|
-
for (var addr of (instance.ipAddresses || [])) {
|
|
290
|
-
if (addr.type === "PRIMARY") {
|
|
291
|
-
publicIp = addr.ipAddress;
|
|
292
|
-
break;
|
|
293
|
-
}
|
|
253
|
+
export async function exportDatabase(cfg, name, opts = {}) {
|
|
254
|
+
if (!opts.connectionUrl) {
|
|
255
|
+
throw new Error("connectionUrl is required to export a GCP database.");
|
|
294
256
|
}
|
|
295
257
|
|
|
296
|
-
var
|
|
297
|
-
var client = await connectPg(connectionUrl);
|
|
258
|
+
var database = dbName(name);
|
|
259
|
+
var client = await connectPg(opts.connectionUrl);
|
|
298
260
|
|
|
299
261
|
try {
|
|
300
262
|
// Get all user tables
|
|
@@ -352,93 +314,46 @@ export async function exportDatabase(cfg, appName, opts = {}) {
|
|
|
352
314
|
}
|
|
353
315
|
}
|
|
354
316
|
|
|
355
|
-
export async function rotateToken(cfg,
|
|
317
|
+
export async function rotateToken(cfg, name, opts = {}) {
|
|
356
318
|
var dbId = opts.dbId;
|
|
357
|
-
var database;
|
|
358
319
|
if (!dbId) {
|
|
359
|
-
|
|
360
|
-
if (!appConfig || !appConfig.dbId) {
|
|
361
|
-
throw new Error(`App ${appName} does not have a database.`);
|
|
362
|
-
}
|
|
363
|
-
dbId = appConfig.dbId;
|
|
364
|
-
database = appConfig.dbName;
|
|
365
|
-
} else {
|
|
366
|
-
database = dbName(appName);
|
|
320
|
+
throw new Error("dbId is required to rotate a GCP database token.");
|
|
367
321
|
}
|
|
368
322
|
|
|
369
|
-
var
|
|
323
|
+
var database = dbName(name);
|
|
370
324
|
var newPassword = randomBytes(24).toString("base64url");
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
break;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
var connectionUrl = publicIp
|
|
386
|
-
? `postgresql://relight:${encodeURIComponent(newPassword)}@${publicIp}:5432/${database}`
|
|
387
|
-
: null;
|
|
388
|
-
|
|
389
|
-
if (!opts.skipAppConfig) {
|
|
390
|
-
if (!appConfig) {
|
|
391
|
-
appConfig = await getAppConfig(cfg, appName);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// Update app config
|
|
395
|
-
if (!appConfig.envKeys) appConfig.envKeys = [];
|
|
396
|
-
if (!appConfig.secretKeys) appConfig.secretKeys = [];
|
|
397
|
-
if (!appConfig.env) appConfig.env = {};
|
|
398
|
-
|
|
399
|
-
appConfig.env["DB_TOKEN"] = "[hidden]";
|
|
400
|
-
if (!appConfig.secretKeys.includes("DB_TOKEN")) appConfig.secretKeys.push("DB_TOKEN");
|
|
401
|
-
appConfig.envKeys = appConfig.envKeys.filter((k) => k !== "DB_TOKEN");
|
|
402
|
-
|
|
403
|
-
if (connectionUrl) {
|
|
404
|
-
appConfig.env["DATABASE_URL"] = connectionUrl;
|
|
405
|
-
if (!appConfig.envKeys.includes("DATABASE_URL")) appConfig.envKeys.push("DATABASE_URL");
|
|
325
|
+
var connectionUrl;
|
|
326
|
+
|
|
327
|
+
if (isSharedInstance(dbId)) {
|
|
328
|
+
// Update via admin connection
|
|
329
|
+
var user = userName(name);
|
|
330
|
+
var { client, ip } = await connectAsAdmin(cfg);
|
|
331
|
+
try {
|
|
332
|
+
await client.query(`ALTER USER ${user} WITH PASSWORD '${newPassword.replace(/'/g, "''")}'`);
|
|
333
|
+
} finally {
|
|
334
|
+
await client.end();
|
|
406
335
|
}
|
|
407
|
-
|
|
408
|
-
|
|
336
|
+
connectionUrl = `postgresql://${user}:${encodeURIComponent(newPassword)}@${ip}:5432/${database}`;
|
|
337
|
+
} else {
|
|
338
|
+
// Legacy: update via Cloud SQL API
|
|
339
|
+
var token = await mintAccessToken(cfg.clientEmail, cfg.privateKey);
|
|
340
|
+
await updateSqlUser(token, cfg.project, dbId, "relight", newPassword);
|
|
341
|
+
var instance = await getSqlInstance(token, cfg.project, dbId);
|
|
342
|
+
var publicIp = getPublicIp(instance);
|
|
343
|
+
connectionUrl = publicIp
|
|
344
|
+
? `postgresql://relight:${encodeURIComponent(newPassword)}@${publicIp}:5432/${database}`
|
|
345
|
+
: null;
|
|
409
346
|
}
|
|
410
347
|
|
|
411
348
|
return { dbToken: newPassword, connectionUrl };
|
|
412
349
|
}
|
|
413
350
|
|
|
414
|
-
export async function resetDatabase(cfg,
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
if (!dbId) {
|
|
418
|
-
var appConfig = await getAppConfig(cfg, appName);
|
|
419
|
-
if (!appConfig || !appConfig.dbId) {
|
|
420
|
-
throw new Error(`App ${appName} does not have a database.`);
|
|
421
|
-
}
|
|
422
|
-
dbId = appConfig.dbId;
|
|
423
|
-
database = appConfig.dbName;
|
|
424
|
-
} else {
|
|
425
|
-
database = dbName(appName);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
var password = await getDbPassword(cfg, appName);
|
|
429
|
-
var token = await mintAccessToken(cfg.clientEmail, cfg.privateKey);
|
|
430
|
-
var instance = await getSqlInstance(token, cfg.project, dbId);
|
|
431
|
-
|
|
432
|
-
var publicIp = null;
|
|
433
|
-
for (var addr of (instance.ipAddresses || [])) {
|
|
434
|
-
if (addr.type === "PRIMARY") {
|
|
435
|
-
publicIp = addr.ipAddress;
|
|
436
|
-
break;
|
|
437
|
-
}
|
|
351
|
+
export async function resetDatabase(cfg, name, opts = {}) {
|
|
352
|
+
if (!opts.connectionUrl) {
|
|
353
|
+
throw new Error("connectionUrl is required to reset a GCP database.");
|
|
438
354
|
}
|
|
439
355
|
|
|
440
|
-
var
|
|
441
|
-
var client = await connectPg(connectionUrl);
|
|
356
|
+
var client = await connectPg(opts.connectionUrl);
|
|
442
357
|
|
|
443
358
|
try {
|
|
444
359
|
var tablesRes = await client.query(
|