relight-cli 0.1.0 → 0.2.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.
Files changed (42) hide show
  1. package/README.md +77 -34
  2. package/package.json +12 -4
  3. package/src/cli.js +305 -1
  4. package/src/commands/apps.js +128 -0
  5. package/src/commands/auth.js +75 -4
  6. package/src/commands/config.js +282 -0
  7. package/src/commands/cost.js +593 -0
  8. package/src/commands/db.js +531 -0
  9. package/src/commands/deploy.js +298 -0
  10. package/src/commands/doctor.js +41 -9
  11. package/src/commands/domains.js +223 -0
  12. package/src/commands/logs.js +111 -0
  13. package/src/commands/open.js +42 -0
  14. package/src/commands/ps.js +121 -0
  15. package/src/commands/scale.js +132 -0
  16. package/src/lib/clouds/aws.js +309 -35
  17. package/src/lib/clouds/cf.js +401 -2
  18. package/src/lib/clouds/gcp.js +234 -3
  19. package/src/lib/clouds/slicervm.js +139 -0
  20. package/src/lib/config.js +40 -0
  21. package/src/lib/docker.js +34 -0
  22. package/src/lib/link.js +20 -5
  23. package/src/lib/providers/aws/app.js +481 -0
  24. package/src/lib/providers/aws/db.js +513 -0
  25. package/src/lib/providers/aws/dns.js +232 -0
  26. package/src/lib/providers/aws/registry.js +59 -0
  27. package/src/lib/providers/cf/app.js +596 -0
  28. package/src/lib/providers/cf/bundle.js +70 -0
  29. package/src/lib/providers/cf/db.js +279 -0
  30. package/src/lib/providers/cf/dns.js +148 -0
  31. package/src/lib/providers/cf/registry.js +17 -0
  32. package/src/lib/providers/gcp/app.js +429 -0
  33. package/src/lib/providers/gcp/db.js +457 -0
  34. package/src/lib/providers/gcp/dns.js +166 -0
  35. package/src/lib/providers/gcp/registry.js +30 -0
  36. package/src/lib/providers/resolve.js +49 -0
  37. package/src/lib/providers/slicervm/app.js +396 -0
  38. package/src/lib/providers/slicervm/db.js +33 -0
  39. package/src/lib/providers/slicervm/dns.js +58 -0
  40. package/src/lib/providers/slicervm/registry.js +7 -0
  41. package/worker-template/package.json +10 -0
  42. package/worker-template/src/index.js +260 -0
@@ -1,4 +1,7 @@
1
1
  var CF_API = "https://api.cloudflare.com/client/v4";
2
+ var CF_REGISTRY = "registry.cloudflare.com";
3
+
4
+ export { CF_REGISTRY };
2
5
 
3
6
  export var TOKEN_URL =
4
7
  "https://dash.cloudflare.com/profile/api-tokens?" +
@@ -13,12 +16,16 @@ export var TOKEN_URL =
13
16
  ) +
14
17
  "&name=relight-cli";
15
18
 
16
- export async function cfApi(method, path, body, apiToken) {
19
+ // --- CF API base ---
20
+
21
+ export async function cfApi(method, path, body, apiToken, contentType) {
17
22
  var headers = {
18
23
  Authorization: `Bearer ${apiToken}`,
19
24
  };
20
25
 
21
- if (body && typeof body === "object") {
26
+ if (contentType) {
27
+ headers["Content-Type"] = contentType;
28
+ } else if (body && typeof body === "object") {
22
29
  headers["Content-Type"] = "application/json";
23
30
  body = JSON.stringify(body);
24
31
  }
@@ -41,6 +48,32 @@ export async function cfApi(method, path, body, apiToken) {
41
48
  return res.text();
42
49
  }
43
50
 
51
+ // --- CF GraphQL Analytics API ---
52
+
53
+ export async function cfGraphQL(apiToken, query, variables) {
54
+ var res = await fetch("https://api.cloudflare.com/client/v4/graphql", {
55
+ method: "POST",
56
+ headers: {
57
+ Authorization: `Bearer ${apiToken}`,
58
+ "Content-Type": "application/json",
59
+ },
60
+ body: JSON.stringify({ query, variables }),
61
+ });
62
+
63
+ if (!res.ok) {
64
+ var text = await res.text();
65
+ throw new Error(`CF GraphQL: ${res.status} ${text}`);
66
+ }
67
+
68
+ var json = await res.json();
69
+ if (json.errors && json.errors.length > 0) {
70
+ throw new Error(`CF GraphQL: ${json.errors.map((e) => e.message).join(", ")}`);
71
+ }
72
+ return json.data;
73
+ }
74
+
75
+ // --- Auth verification ---
76
+
44
77
  export async function verifyToken(apiToken) {
45
78
  return cfApi("GET", "/user/tokens/verify", null, apiToken);
46
79
  }
@@ -50,6 +83,299 @@ export async function listAccounts(apiToken) {
50
83
  return res.result || [];
51
84
  }
52
85
 
86
+ // --- Registry ---
87
+
88
+ export async function getRegistryCredentials(accountId, apiToken) {
89
+ var res = await cfApi(
90
+ "POST",
91
+ `/accounts/${accountId}/containers/registries/${CF_REGISTRY}/credentials`,
92
+ { permissions: ["push", "pull"], expiration_minutes: 15 },
93
+ apiToken
94
+ );
95
+ return res.result;
96
+ }
97
+
98
+ // --- Worker scripts ---
99
+
100
+ export async function uploadWorker(accountId, apiToken, scriptName, code, metadata) {
101
+ var form = new FormData();
102
+ form.append(
103
+ "metadata",
104
+ new Blob([JSON.stringify(metadata)], { type: "application/json" })
105
+ );
106
+ form.append(
107
+ "index.js",
108
+ new Blob([code], { type: "application/javascript+module" }),
109
+ "index.js"
110
+ );
111
+
112
+ var res = await fetch(
113
+ `${CF_API}/accounts/${accountId}/workers/scripts/${scriptName}`,
114
+ {
115
+ method: "PUT",
116
+ headers: { Authorization: `Bearer ${apiToken}` },
117
+ body: form,
118
+ }
119
+ );
120
+
121
+ if (!res.ok) {
122
+ var text = await res.text();
123
+ throw new Error(`Worker upload failed: ${res.status} ${text}`);
124
+ }
125
+
126
+ return res.json();
127
+ }
128
+
129
+ export async function deleteWorker(accountId, apiToken, scriptName) {
130
+ return cfApi(
131
+ "DELETE",
132
+ `/accounts/${accountId}/workers/scripts/${scriptName}`,
133
+ null,
134
+ apiToken
135
+ );
136
+ }
137
+
138
+ export async function patchWorkerSettings(accountId, apiToken, scriptName, settings) {
139
+ var form = new FormData();
140
+ form.append(
141
+ "settings",
142
+ new Blob([JSON.stringify(settings)], { type: "application/json" })
143
+ );
144
+
145
+ var res = await fetch(
146
+ `${CF_API}/accounts/${accountId}/workers/scripts/${scriptName}/settings`,
147
+ {
148
+ method: "PATCH",
149
+ headers: { Authorization: `Bearer ${apiToken}` },
150
+ body: form,
151
+ }
152
+ );
153
+
154
+ if (!res.ok) {
155
+ var text = await res.text();
156
+ throw new Error(`Settings update failed: ${res.status} ${text}`);
157
+ }
158
+
159
+ return res.json();
160
+ }
161
+
162
+ export async function listWorkerScripts(accountId, apiToken) {
163
+ var res = await cfApi(
164
+ "GET",
165
+ `/accounts/${accountId}/workers/scripts`,
166
+ null,
167
+ apiToken
168
+ );
169
+ return res.result || [];
170
+ }
171
+
172
+ export async function getWorkerSettings(accountId, apiToken, scriptName) {
173
+ var res = await cfApi(
174
+ "GET",
175
+ `/accounts/${accountId}/workers/scripts/${scriptName}/settings`,
176
+ null,
177
+ apiToken
178
+ );
179
+ return res.result;
180
+ }
181
+
182
+ // --- Container applications ---
183
+
184
+ export async function getDONamespaceId(accountId, apiToken, scriptName, className) {
185
+ var res = await cfApi(
186
+ "GET",
187
+ `/accounts/${accountId}/workers/durable_objects/namespaces`,
188
+ null,
189
+ apiToken
190
+ );
191
+ var namespaces = res.result || [];
192
+ var ns = namespaces.find(
193
+ (n) => n.script === scriptName && n.class === className
194
+ );
195
+ return ns ? ns.id : null;
196
+ }
197
+
198
+ export async function listContainerApps(accountId, apiToken) {
199
+ var res = await cfApi(
200
+ "GET",
201
+ `/accounts/${accountId}/containers/applications`,
202
+ null,
203
+ apiToken
204
+ );
205
+ return res.result || [];
206
+ }
207
+
208
+ export async function findContainerApp(accountId, apiToken, name) {
209
+ var apps = await listContainerApps(accountId, apiToken);
210
+ return apps.find((a) => a.name === name) || null;
211
+ }
212
+
213
+ export async function createContainerApp(accountId, apiToken, app) {
214
+ var res = await cfApi(
215
+ "POST",
216
+ `/accounts/${accountId}/containers/applications`,
217
+ app,
218
+ apiToken
219
+ );
220
+ return res.result;
221
+ }
222
+
223
+ export async function deleteContainerApp(accountId, apiToken, appId) {
224
+ return cfApi(
225
+ "DELETE",
226
+ `/accounts/${accountId}/containers/applications/${appId}`,
227
+ null,
228
+ apiToken
229
+ );
230
+ }
231
+
232
+ export async function modifyContainerApp(accountId, apiToken, appId, changes) {
233
+ var res = await cfApi(
234
+ "PATCH",
235
+ `/accounts/${accountId}/containers/applications/${appId}`,
236
+ changes,
237
+ apiToken
238
+ );
239
+ return res.result;
240
+ }
241
+
242
+ export async function createRollout(accountId, apiToken, appId, rollout) {
243
+ var res = await cfApi(
244
+ "POST",
245
+ `/accounts/${accountId}/containers/applications/${appId}/rollouts`,
246
+ rollout,
247
+ apiToken
248
+ );
249
+ return res.result;
250
+ }
251
+
252
+ // --- Tail/logs ---
253
+
254
+ export async function createTail(accountId, apiToken, scriptName) {
255
+ var res = await cfApi(
256
+ "POST",
257
+ `/accounts/${accountId}/workers/scripts/${scriptName}/tails`,
258
+ {},
259
+ apiToken
260
+ );
261
+ return res.result;
262
+ }
263
+
264
+ export async function deleteTail(accountId, apiToken, scriptName, tailId) {
265
+ return cfApi(
266
+ "DELETE",
267
+ `/accounts/${accountId}/workers/scripts/${scriptName}/tails/${tailId}`,
268
+ null,
269
+ apiToken
270
+ );
271
+ }
272
+
273
+ // --- Zones ---
274
+
275
+ export async function listZones(accountId, apiToken) {
276
+ var all = [];
277
+ var page = 1;
278
+ while (true) {
279
+ var res = await cfApi(
280
+ "GET",
281
+ `/zones?account.id=${accountId}&per_page=50&status=active&page=${page}`,
282
+ null,
283
+ apiToken
284
+ );
285
+ var zones = res.result || [];
286
+ all.push(...zones);
287
+ if (zones.length < 50) break;
288
+ page++;
289
+ }
290
+ return all;
291
+ }
292
+
293
+ export function findZoneForHostname(zones, hostname) {
294
+ var match = null;
295
+ for (var zone of zones) {
296
+ if (hostname === zone.name || hostname.endsWith("." + zone.name)) {
297
+ if (!match || zone.name.length > match.name.length) {
298
+ match = zone;
299
+ }
300
+ }
301
+ }
302
+ return match;
303
+ }
304
+
305
+ // --- DNS records ---
306
+
307
+ export async function listDnsRecords(accountId, apiToken, zoneId, params) {
308
+ var qs = new URLSearchParams(params || {}).toString();
309
+ var path = `/zones/${zoneId}/dns_records${qs ? "?" + qs : ""}`;
310
+ var res = await cfApi("GET", path, null, apiToken);
311
+ return res.result || [];
312
+ }
313
+
314
+ export async function createDnsRecord(accountId, apiToken, zoneId, record) {
315
+ return cfApi(
316
+ "POST",
317
+ `/zones/${zoneId}/dns_records`,
318
+ record,
319
+ apiToken
320
+ );
321
+ }
322
+
323
+ export async function deleteDnsRecord(accountId, apiToken, zoneId, recordId) {
324
+ return cfApi(
325
+ "DELETE",
326
+ `/zones/${zoneId}/dns_records/${recordId}`,
327
+ null,
328
+ apiToken
329
+ );
330
+ }
331
+
332
+ // --- Workers custom domains ---
333
+
334
+ export async function addWorkerDomain(accountId, apiToken, scriptName, hostname, zoneId) {
335
+ return cfApi(
336
+ "PUT",
337
+ `/accounts/${accountId}/workers/domains`,
338
+ {
339
+ hostname,
340
+ zone_id: zoneId,
341
+ service: scriptName,
342
+ environment: "production",
343
+ },
344
+ apiToken
345
+ );
346
+ }
347
+
348
+ export async function removeWorkerDomain(accountId, apiToken, hostname) {
349
+ var res = await cfApi(
350
+ "GET",
351
+ `/accounts/${accountId}/workers/domains`,
352
+ null,
353
+ apiToken
354
+ );
355
+ var domains = res.result || [];
356
+ var domain = domains.find((d) => d.hostname === hostname);
357
+ if (!domain) return;
358
+
359
+ return cfApi(
360
+ "DELETE",
361
+ `/accounts/${accountId}/workers/domains/${domain.id}`,
362
+ null,
363
+ apiToken
364
+ );
365
+ }
366
+
367
+ export async function listWorkerDomainsForService(accountId, apiToken, scriptName) {
368
+ var res = await cfApi(
369
+ "GET",
370
+ `/accounts/${accountId}/workers/domains`,
371
+ null,
372
+ apiToken
373
+ );
374
+ return (res.result || []).filter((d) => d.service === scriptName);
375
+ }
376
+
377
+ // --- Workers subdomain ---
378
+
53
379
  export async function getWorkersSubdomain(accountId, apiToken) {
54
380
  try {
55
381
  var res = await cfApi(
@@ -63,3 +389,76 @@ export async function getWorkersSubdomain(accountId, apiToken) {
63
389
  return null;
64
390
  }
65
391
  }
392
+
393
+ export async function enableWorkerSubdomain(accountId, apiToken, scriptName) {
394
+ return cfApi(
395
+ "POST",
396
+ `/accounts/${accountId}/workers/services/${scriptName}/environments/production/subdomain`,
397
+ { enabled: true },
398
+ apiToken
399
+ );
400
+ }
401
+
402
+ // --- D1 databases ---
403
+
404
+ export async function createD1Database(accountId, apiToken, name, { locationHint, jurisdiction } = {}) {
405
+ var body = { name };
406
+ if (jurisdiction) body.jurisdiction = jurisdiction;
407
+ else if (locationHint) body.primary_location_hint = locationHint;
408
+ var res = await cfApi(
409
+ "POST",
410
+ `/accounts/${accountId}/d1/database`,
411
+ body,
412
+ apiToken
413
+ );
414
+ return res.result;
415
+ }
416
+
417
+ export async function deleteD1Database(accountId, apiToken, dbId) {
418
+ return cfApi(
419
+ "DELETE",
420
+ `/accounts/${accountId}/d1/database/${dbId}`,
421
+ null,
422
+ apiToken
423
+ );
424
+ }
425
+
426
+ export async function getD1Database(accountId, apiToken, dbId) {
427
+ var res = await cfApi(
428
+ "GET",
429
+ `/accounts/${accountId}/d1/database/${dbId}`,
430
+ null,
431
+ apiToken
432
+ );
433
+ return res.result;
434
+ }
435
+
436
+ export async function queryD1(accountId, apiToken, dbId, sql, params) {
437
+ var body = { sql };
438
+ if (params) body.params = params;
439
+ var res = await cfApi(
440
+ "POST",
441
+ `/accounts/${accountId}/d1/database/${dbId}/query`,
442
+ body,
443
+ apiToken
444
+ );
445
+ return res.result;
446
+ }
447
+
448
+ export async function exportD1(accountId, apiToken, dbId, body) {
449
+ return cfApi(
450
+ "POST",
451
+ `/accounts/${accountId}/d1/database/${dbId}/export`,
452
+ body,
453
+ apiToken
454
+ );
455
+ }
456
+
457
+ export async function importD1(accountId, apiToken, dbId, body) {
458
+ return cfApi(
459
+ "POST",
460
+ `/accounts/${accountId}/d1/database/${dbId}/import`,
461
+ body,
462
+ apiToken
463
+ );
464
+ }
@@ -1,11 +1,17 @@
1
1
  import { createSign } from "crypto";
2
2
  import { readFileSync } from "fs";
3
3
 
4
- var RUN_API = "https://run.googleapis.com/v2";
4
+ export var RUN_API = "https://run.googleapis.com/v2";
5
5
  var CRM_API = "https://cloudresourcemanager.googleapis.com/v1";
6
6
  var TOKEN_URI = "https://oauth2.googleapis.com/token";
7
7
  var SCOPE = "https://www.googleapis.com/auth/cloud-platform";
8
8
 
9
+ export var AR_API = "https://artifactregistry.googleapis.com/v1";
10
+ export var SQLADMIN_API = "https://sqladmin.googleapis.com/v1";
11
+ export var DNS_API = "https://dns.googleapis.com/dns/v1";
12
+ export var LOGGING_API = "https://logging.googleapis.com/v2";
13
+ export var MONITORING_API = "https://monitoring.googleapis.com/v3";
14
+
9
15
  // --- Service account key file ---
10
16
 
11
17
  export function readKeyFile(path) {
@@ -25,7 +31,7 @@ export function readKeyFile(path) {
25
31
  };
26
32
  }
27
33
 
28
- // --- JWT access token ---
34
+ // --- JWT -> access token ---
29
35
 
30
36
  export async function mintAccessToken(clientEmail, privateKey) {
31
37
  var now = Math.floor(Date.now() / 1000);
@@ -95,10 +101,51 @@ export async function gcpApi(method, url, body, token) {
95
101
  return res.json();
96
102
  }
97
103
 
104
+ // --- LRO polling ---
105
+
106
+ export async function waitForOperation(token, operationName, apiBase) {
107
+ var url = apiBase
108
+ ? `${apiBase}/${operationName}`
109
+ : `https://run.googleapis.com/v2/${operationName}`;
110
+ while (true) {
111
+ var op = await gcpApi("GET", url, null, token);
112
+ if (op.done) {
113
+ if (op.error) {
114
+ throw new Error(`Operation failed: ${op.error.message || JSON.stringify(op.error)}`);
115
+ }
116
+ return op.response || op;
117
+ }
118
+ await new Promise((r) => setTimeout(r, 2000));
119
+ }
120
+ }
121
+
122
+ export async function waitForSqlOperation(token, project, opName) {
123
+ while (true) {
124
+ var op = await gcpApi(
125
+ "GET",
126
+ `${SQLADMIN_API}/projects/${project}/operations/${opName}`,
127
+ null,
128
+ token
129
+ );
130
+ if (op.status === "DONE") {
131
+ if (op.error) {
132
+ var msgs = (op.error.errors || []).map((e) => e.message).join(", ");
133
+ throw new Error(`SQL operation failed: ${msgs || JSON.stringify(op.error)}`);
134
+ }
135
+ return op;
136
+ }
137
+ await new Promise((r) => setTimeout(r, 3000));
138
+ }
139
+ }
140
+
141
+ // --- Project verification ---
142
+
98
143
  export async function verifyProject(token, project) {
99
144
  return gcpApi("GET", `${CRM_API}/projects/${project}`, null, token);
100
145
  }
101
146
 
147
+ // --- Cloud Run ---
148
+
102
149
  export async function listRegions(token, project) {
103
150
  var res = await gcpApi(
104
151
  "GET",
@@ -117,4 +164,188 @@ export async function listServices(token, project, region) {
117
164
  token
118
165
  );
119
166
  return res.services || [];
120
- }
167
+ }
168
+
169
+ export async function listAllServices(token, project) {
170
+ var res = await gcpApi(
171
+ "GET",
172
+ `${RUN_API}/projects/${project}/locations/-/services`,
173
+ null,
174
+ token
175
+ );
176
+ return res.services || [];
177
+ }
178
+
179
+ export async function getService(token, serviceName) {
180
+ return gcpApi("GET", `${RUN_API}/${serviceName}`, null, token);
181
+ }
182
+
183
+ export async function createService(token, project, region, serviceId, body) {
184
+ var op = await gcpApi(
185
+ "POST",
186
+ `${RUN_API}/projects/${project}/locations/${region}/services?serviceId=${serviceId}`,
187
+ body,
188
+ token
189
+ );
190
+ return waitForOperation(token, op.name);
191
+ }
192
+
193
+ export async function updateService(token, serviceName, body) {
194
+ var op = await gcpApi("PATCH", `${RUN_API}/${serviceName}`, body, token);
195
+ return waitForOperation(token, op.name);
196
+ }
197
+
198
+ export async function deleteService(token, serviceName) {
199
+ var op = await gcpApi("DELETE", `${RUN_API}/${serviceName}`, null, token);
200
+ return waitForOperation(token, op.name);
201
+ }
202
+
203
+ export async function setIamPolicy(token, serviceName, policy) {
204
+ return gcpApi(
205
+ "POST",
206
+ `${RUN_API}/${serviceName}:setIamPolicy`,
207
+ { policy },
208
+ token
209
+ );
210
+ }
211
+
212
+ // --- Artifact Registry ---
213
+
214
+ export async function getRepository(token, project, location, repoName) {
215
+ return gcpApi(
216
+ "GET",
217
+ `${AR_API}/projects/${project}/locations/${location}/repositories/${repoName}`,
218
+ null,
219
+ token
220
+ );
221
+ }
222
+
223
+ export async function createRepository(token, project, location, repoName) {
224
+ var op = await gcpApi(
225
+ "POST",
226
+ `${AR_API}/projects/${project}/locations/${location}/repositories?repositoryId=${repoName}`,
227
+ { format: "DOCKER" },
228
+ token
229
+ );
230
+ if (op.done) return op.response || op;
231
+ return waitForOperation(token, op.name, AR_API);
232
+ }
233
+
234
+ // --- Cloud SQL ---
235
+
236
+ export async function createSqlInstance(token, project, body) {
237
+ var op = await gcpApi(
238
+ "POST",
239
+ `${SQLADMIN_API}/projects/${project}/instances`,
240
+ body,
241
+ token
242
+ );
243
+ return waitForSqlOperation(token, project, op.name);
244
+ }
245
+
246
+ export async function getSqlInstance(token, project, instanceName) {
247
+ return gcpApi(
248
+ "GET",
249
+ `${SQLADMIN_API}/projects/${project}/instances/${instanceName}`,
250
+ null,
251
+ token
252
+ );
253
+ }
254
+
255
+ export async function deleteSqlInstance(token, project, instanceName) {
256
+ var op = await gcpApi(
257
+ "DELETE",
258
+ `${SQLADMIN_API}/projects/${project}/instances/${instanceName}`,
259
+ null,
260
+ token
261
+ );
262
+ return waitForSqlOperation(token, project, op.name);
263
+ }
264
+
265
+ export async function createSqlDatabase(token, project, instanceName, dbName) {
266
+ var op = await gcpApi(
267
+ "POST",
268
+ `${SQLADMIN_API}/projects/${project}/instances/${instanceName}/databases`,
269
+ { name: dbName },
270
+ token
271
+ );
272
+ return waitForSqlOperation(token, project, op.name);
273
+ }
274
+
275
+ export async function deleteSqlDatabase(token, project, instanceName, dbName) {
276
+ var op = await gcpApi(
277
+ "DELETE",
278
+ `${SQLADMIN_API}/projects/${project}/instances/${instanceName}/databases/${dbName}`,
279
+ null,
280
+ token
281
+ );
282
+ return waitForSqlOperation(token, project, op.name);
283
+ }
284
+
285
+ export async function createSqlUser(token, project, instanceName, userName, password) {
286
+ var op = await gcpApi(
287
+ "POST",
288
+ `${SQLADMIN_API}/projects/${project}/instances/${instanceName}/users`,
289
+ { name: userName, password },
290
+ token
291
+ );
292
+ return waitForSqlOperation(token, project, op.name);
293
+ }
294
+
295
+ export async function updateSqlUser(token, project, instanceName, userName, password) {
296
+ var op = await gcpApi(
297
+ "PUT",
298
+ `${SQLADMIN_API}/projects/${project}/instances/${instanceName}/users?name=${encodeURIComponent(userName)}`,
299
+ { name: userName, password },
300
+ token
301
+ );
302
+ return waitForSqlOperation(token, project, op.name);
303
+ }
304
+
305
+ // --- Cloud DNS ---
306
+
307
+ export async function listManagedZones(token, project) {
308
+ var res = await gcpApi(
309
+ "GET",
310
+ `${DNS_API}/projects/${project}/managedZones`,
311
+ null,
312
+ token
313
+ );
314
+ return res.managedZones || [];
315
+ }
316
+
317
+ export async function createDnsChange(token, project, zoneName, change) {
318
+ return gcpApi(
319
+ "POST",
320
+ `${DNS_API}/projects/${project}/managedZones/${zoneName}/changes`,
321
+ change,
322
+ token
323
+ );
324
+ }
325
+
326
+ export async function listResourceRecordSets(token, project, zoneName) {
327
+ var res = await gcpApi(
328
+ "GET",
329
+ `${DNS_API}/projects/${project}/managedZones/${zoneName}/rrsets`,
330
+ null,
331
+ token
332
+ );
333
+ return res.rrsets || [];
334
+ }
335
+
336
+ // --- Cloud Logging ---
337
+
338
+ export async function listLogEntries(token, body) {
339
+ return gcpApi("POST", `${LOGGING_API}/entries:list`, body, token);
340
+ }
341
+
342
+ // --- Cloud Monitoring ---
343
+
344
+ export async function queryTimeSeries(token, project, body) {
345
+ return gcpApi(
346
+ "POST",
347
+ `${MONITORING_API}/projects/${project}/timeSeries:query`,
348
+ body,
349
+ token
350
+ );
351
+ }