disk 0.8.0 → 0.8.9
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/{LICENSE.md → LICENSE} +10 -9
- package/README.md +111 -78
- package/dist/cli.js +592 -0
- package/dist/index.cjs +421 -0
- package/dist/index.d.cts +1145 -0
- package/dist/index.d.ts +1145 -0
- package/dist/index.js +372 -0
- package/package.json +39 -32
- package/lib/disk.js +0 -381
- package/lib/partition.js +0 -117
package/dist/cli.js
ADDED
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// bin/cli.ts
|
|
4
|
+
import { createRequire as createRequire2 } from "module";
|
|
5
|
+
import { Command, Option } from "commander";
|
|
6
|
+
|
|
7
|
+
// src/client.ts
|
|
8
|
+
import createClient from "openapi-fetch";
|
|
9
|
+
|
|
10
|
+
// src/errors.ts
|
|
11
|
+
var ArchilApiError = class extends Error {
|
|
12
|
+
status;
|
|
13
|
+
code;
|
|
14
|
+
constructor(message, status, code) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "ArchilApiError";
|
|
17
|
+
this.status = status;
|
|
18
|
+
this.code = code;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// src/regions.ts
|
|
23
|
+
var REGION_URLS = {
|
|
24
|
+
"aws-us-east-1": "https://control.green.us-east-1.aws.prod.archil.com",
|
|
25
|
+
"aws-us-west-2": "https://control.green.us-west-2.aws.prod.archil.com",
|
|
26
|
+
"aws-eu-west-1": "https://control.green.eu-west-1.aws.prod.archil.com",
|
|
27
|
+
"gcp-us-central1": "https://control.blue.us-central1.gcp.prod.archil.com"
|
|
28
|
+
};
|
|
29
|
+
function resolveBaseUrl(region) {
|
|
30
|
+
const url = REGION_URLS[region];
|
|
31
|
+
if (!url) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Unknown region "${region}". Valid regions: ${Object.keys(REGION_URLS).join(", ")}`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return url;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/client.ts
|
|
40
|
+
function createApiClient(opts) {
|
|
41
|
+
const baseUrl = opts.baseUrl ?? resolveBaseUrl(opts.region);
|
|
42
|
+
return createClient({
|
|
43
|
+
baseUrl,
|
|
44
|
+
headers: {
|
|
45
|
+
Authorization: `key-${opts.apiKey.replace(/^key-/, "")}`
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function unwrap(promise) {
|
|
50
|
+
const { data: body, error, response } = await promise;
|
|
51
|
+
if (error || !body) {
|
|
52
|
+
const errBody = error;
|
|
53
|
+
throw new ArchilApiError(
|
|
54
|
+
errBody?.error ?? `API request failed with status ${response.status}`,
|
|
55
|
+
response.status
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (!body.success) {
|
|
59
|
+
throw new ArchilApiError(
|
|
60
|
+
body.error ?? "Unknown API error",
|
|
61
|
+
response.status
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return body.data;
|
|
65
|
+
}
|
|
66
|
+
async function unwrapEmpty(promise) {
|
|
67
|
+
const { data: body, error, response } = await promise;
|
|
68
|
+
if (error || !body) {
|
|
69
|
+
const errBody = error;
|
|
70
|
+
throw new ArchilApiError(
|
|
71
|
+
errBody?.error ?? `API request failed with status ${response.status}`,
|
|
72
|
+
response.status
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (!body.success) {
|
|
76
|
+
throw new ArchilApiError(
|
|
77
|
+
body.error ?? "Unknown API error",
|
|
78
|
+
response.status
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/disk.ts
|
|
84
|
+
import { createRequire } from "module";
|
|
85
|
+
import { pathToFileURL } from "url";
|
|
86
|
+
var Disk = class {
|
|
87
|
+
id;
|
|
88
|
+
name;
|
|
89
|
+
organization;
|
|
90
|
+
status;
|
|
91
|
+
provider;
|
|
92
|
+
region;
|
|
93
|
+
createdAt;
|
|
94
|
+
fsHandlerStatus;
|
|
95
|
+
lastAccessed;
|
|
96
|
+
dataSize;
|
|
97
|
+
monthlyUsage;
|
|
98
|
+
mounts;
|
|
99
|
+
metrics;
|
|
100
|
+
connectedClients;
|
|
101
|
+
authorizedUsers;
|
|
102
|
+
/** @internal */
|
|
103
|
+
_client;
|
|
104
|
+
/** @internal */
|
|
105
|
+
_archilRegion;
|
|
106
|
+
/** @internal */
|
|
107
|
+
constructor(data, client, archilRegion) {
|
|
108
|
+
this.id = data.id;
|
|
109
|
+
this.name = data.name;
|
|
110
|
+
this.organization = data.organization;
|
|
111
|
+
this.status = data.status;
|
|
112
|
+
this.provider = data.provider;
|
|
113
|
+
this.region = data.region;
|
|
114
|
+
this.createdAt = data.createdAt;
|
|
115
|
+
this.fsHandlerStatus = data.fsHandlerStatus;
|
|
116
|
+
this.lastAccessed = data.lastAccessed;
|
|
117
|
+
this.dataSize = data.dataSize;
|
|
118
|
+
this.monthlyUsage = data.monthlyUsage;
|
|
119
|
+
this.mounts = data.mounts;
|
|
120
|
+
this.metrics = data.metrics;
|
|
121
|
+
this.connectedClients = data.connectedClients;
|
|
122
|
+
this.authorizedUsers = data.authorizedUsers;
|
|
123
|
+
this._client = client;
|
|
124
|
+
this._archilRegion = archilRegion;
|
|
125
|
+
}
|
|
126
|
+
toJSON() {
|
|
127
|
+
return {
|
|
128
|
+
id: this.id,
|
|
129
|
+
name: this.name,
|
|
130
|
+
organization: this.organization,
|
|
131
|
+
status: this.status,
|
|
132
|
+
provider: this.provider,
|
|
133
|
+
region: this.region,
|
|
134
|
+
createdAt: this.createdAt,
|
|
135
|
+
fsHandlerStatus: this.fsHandlerStatus,
|
|
136
|
+
lastAccessed: this.lastAccessed,
|
|
137
|
+
dataSize: this.dataSize,
|
|
138
|
+
monthlyUsage: this.monthlyUsage,
|
|
139
|
+
mounts: this.mounts,
|
|
140
|
+
metrics: this.metrics,
|
|
141
|
+
connectedClients: this.connectedClients,
|
|
142
|
+
authorizedUsers: this.authorizedUsers
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
async addUser(user) {
|
|
146
|
+
return unwrap(
|
|
147
|
+
this._client.POST("/api/disks/{id}/users", {
|
|
148
|
+
params: { path: { id: this.id } },
|
|
149
|
+
body: user
|
|
150
|
+
})
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
async removeUser(userType, identifier) {
|
|
154
|
+
await unwrapEmpty(
|
|
155
|
+
this._client.DELETE("/api/disks/{id}/users/{userType}", {
|
|
156
|
+
params: {
|
|
157
|
+
path: { id: this.id, userType },
|
|
158
|
+
query: { identifier }
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
async createToken(nickname) {
|
|
164
|
+
const user = await unwrap(
|
|
165
|
+
this._client.POST("/api/disks/{id}/users", {
|
|
166
|
+
params: { path: { id: this.id } },
|
|
167
|
+
body: { type: "token", nickname }
|
|
168
|
+
})
|
|
169
|
+
);
|
|
170
|
+
if (!user.token || !user.identifier) {
|
|
171
|
+
throw new Error("Server did not return a generated token");
|
|
172
|
+
}
|
|
173
|
+
return user;
|
|
174
|
+
}
|
|
175
|
+
async removeTokenUser(identifier) {
|
|
176
|
+
await this.removeUser("token", identifier);
|
|
177
|
+
}
|
|
178
|
+
async delete() {
|
|
179
|
+
await unwrapEmpty(
|
|
180
|
+
this._client.DELETE("/api/disks/{id}", {
|
|
181
|
+
params: { path: { id: this.id } }
|
|
182
|
+
})
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Execute a command in a container with this disk mounted.
|
|
187
|
+
* Blocks until the command completes and returns stdout, stderr, and exit code.
|
|
188
|
+
*/
|
|
189
|
+
async exec(command) {
|
|
190
|
+
return unwrap(
|
|
191
|
+
this._client.POST("/api/disks/{id}/exec", {
|
|
192
|
+
params: { path: { id: this.id } },
|
|
193
|
+
body: { command }
|
|
194
|
+
})
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Connect to this disk's data plane via the native ArchilClient.
|
|
199
|
+
*
|
|
200
|
+
* Requires the native module to be available (platform-specific .node binary).
|
|
201
|
+
*/
|
|
202
|
+
async mount(opts) {
|
|
203
|
+
let ArchilClient;
|
|
204
|
+
try {
|
|
205
|
+
const base = typeof import.meta !== "undefined" && import.meta.url ? import.meta.url : pathToFileURL(__filename).href;
|
|
206
|
+
const nativeRequire = createRequire(base);
|
|
207
|
+
const native = nativeRequire("@archildata/native");
|
|
208
|
+
ArchilClient = native.ArchilClient;
|
|
209
|
+
} catch {
|
|
210
|
+
throw new Error(
|
|
211
|
+
"Native client not available. Install @archildata/native (platform-specific binary) or use ArchilClient.connect() directly."
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
return ArchilClient.connect({
|
|
215
|
+
region: this._archilRegion,
|
|
216
|
+
diskName: `${this.organization}/${this.name}`,
|
|
217
|
+
authToken: opts?.authToken,
|
|
218
|
+
logLevel: opts?.logLevel,
|
|
219
|
+
serverAddress: opts?.serverAddress,
|
|
220
|
+
insecure: opts?.insecure
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// src/disks.ts
|
|
226
|
+
var Disks = class {
|
|
227
|
+
/** @internal */
|
|
228
|
+
_client;
|
|
229
|
+
/** @internal */
|
|
230
|
+
_region;
|
|
231
|
+
/** @internal */
|
|
232
|
+
constructor(client, region) {
|
|
233
|
+
this._client = client;
|
|
234
|
+
this._region = region;
|
|
235
|
+
}
|
|
236
|
+
async list(opts) {
|
|
237
|
+
const data = await unwrap(
|
|
238
|
+
this._client.GET("/api/disks", {
|
|
239
|
+
params: { query: { limit: opts?.limit, cursor: opts?.cursor, name: opts?.name } }
|
|
240
|
+
})
|
|
241
|
+
);
|
|
242
|
+
return data.map(
|
|
243
|
+
(d) => new Disk(d, this._client, this._region)
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
async get(id) {
|
|
247
|
+
const data = await unwrap(
|
|
248
|
+
this._client.GET("/api/disks/{id}", {
|
|
249
|
+
params: { path: { id } }
|
|
250
|
+
})
|
|
251
|
+
);
|
|
252
|
+
return new Disk(data, this._client, this._region);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Create a new disk with an auto-generated mount token.
|
|
256
|
+
*
|
|
257
|
+
* Returns the Disk, the one-time token (save it — it cannot be retrieved
|
|
258
|
+
* again), and the token identifier for later management.
|
|
259
|
+
*/
|
|
260
|
+
async create(req) {
|
|
261
|
+
const created = await unwrap(
|
|
262
|
+
this._client.POST("/api/disks", { body: req })
|
|
263
|
+
);
|
|
264
|
+
const resp = created;
|
|
265
|
+
if (!resp.diskId) {
|
|
266
|
+
throw new Error("API returned success but no diskId");
|
|
267
|
+
}
|
|
268
|
+
const authorizedUsers = resp.authorizedUsers ?? [];
|
|
269
|
+
const tokenUser = authorizedUsers.find((u) => u.token);
|
|
270
|
+
const disk = await this.get(resp.diskId);
|
|
271
|
+
return {
|
|
272
|
+
disk,
|
|
273
|
+
token: tokenUser?.token ?? null,
|
|
274
|
+
tokenIdentifier: tokenUser?.identifier ?? null,
|
|
275
|
+
authorizedUsers
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// src/tokens.ts
|
|
281
|
+
var Tokens = class {
|
|
282
|
+
/** @internal */
|
|
283
|
+
_client;
|
|
284
|
+
/** @internal */
|
|
285
|
+
constructor(client) {
|
|
286
|
+
this._client = client;
|
|
287
|
+
}
|
|
288
|
+
async list(opts) {
|
|
289
|
+
const data = await unwrap(
|
|
290
|
+
this._client.GET("/api/tokens", {
|
|
291
|
+
params: { query: { limit: opts?.limit, cursor: opts?.cursor } }
|
|
292
|
+
})
|
|
293
|
+
);
|
|
294
|
+
return data.tokens ?? [];
|
|
295
|
+
}
|
|
296
|
+
async create(req) {
|
|
297
|
+
const data = await unwrap(
|
|
298
|
+
this._client.POST("/api/tokens", { body: req })
|
|
299
|
+
);
|
|
300
|
+
return data;
|
|
301
|
+
}
|
|
302
|
+
async delete(id) {
|
|
303
|
+
await unwrapEmpty(
|
|
304
|
+
this._client.DELETE("/api/tokens/{id}", {
|
|
305
|
+
params: { path: { id } }
|
|
306
|
+
})
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// src/archil.ts
|
|
312
|
+
var Archil = class {
|
|
313
|
+
disks;
|
|
314
|
+
tokens;
|
|
315
|
+
constructor(opts = {}) {
|
|
316
|
+
const apiKey = opts.apiKey ?? process.env.ARCHIL_API_KEY;
|
|
317
|
+
const region = opts.region ?? process.env.ARCHIL_REGION;
|
|
318
|
+
if (!apiKey) {
|
|
319
|
+
throw new Error("Missing API key: pass apiKey in options or set ARCHIL_API_KEY environment variable");
|
|
320
|
+
}
|
|
321
|
+
if (!region) {
|
|
322
|
+
throw new Error("Missing region: pass region in options or set ARCHIL_REGION environment variable");
|
|
323
|
+
}
|
|
324
|
+
const client = createApiClient({
|
|
325
|
+
apiKey,
|
|
326
|
+
region,
|
|
327
|
+
baseUrl: opts.baseUrl
|
|
328
|
+
});
|
|
329
|
+
this.disks = new Disks(client, region);
|
|
330
|
+
this.tokens = new Tokens(client);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
// src/index.ts
|
|
335
|
+
var _options;
|
|
336
|
+
var _instance;
|
|
337
|
+
function configure(options) {
|
|
338
|
+
_options = options;
|
|
339
|
+
_instance = void 0;
|
|
340
|
+
}
|
|
341
|
+
function archil() {
|
|
342
|
+
if (!_instance) {
|
|
343
|
+
_instance = new Archil(_options);
|
|
344
|
+
}
|
|
345
|
+
return _instance;
|
|
346
|
+
}
|
|
347
|
+
function createDisk(req) {
|
|
348
|
+
return archil().disks.create(req);
|
|
349
|
+
}
|
|
350
|
+
function listDisks(opts) {
|
|
351
|
+
return archil().disks.list(opts);
|
|
352
|
+
}
|
|
353
|
+
function getDisk(id) {
|
|
354
|
+
return archil().disks.get(id);
|
|
355
|
+
}
|
|
356
|
+
function listApiKeys(opts) {
|
|
357
|
+
return archil().tokens.list(opts);
|
|
358
|
+
}
|
|
359
|
+
function createApiKey(req) {
|
|
360
|
+
return archil().tokens.create(req);
|
|
361
|
+
}
|
|
362
|
+
function deleteApiKey(id) {
|
|
363
|
+
return archil().tokens.delete(id);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// bin/cli.ts
|
|
367
|
+
var pkg = createRequire2(import.meta.url)("../package.json");
|
|
368
|
+
var program = new Command();
|
|
369
|
+
program.name("disk").description("Manage Archil disks from the command line").version(pkg.version).addOption(new Option("-k, --api-key <key>", "Archil API key").env("ARCHIL_API_KEY")).addOption(new Option("-r, --region <region>", "Archil region").env("ARCHIL_REGION")).addOption(new Option("--base-url <url>", "Override control-plane base URL")).hook("preAction", () => {
|
|
370
|
+
const opts = program.opts();
|
|
371
|
+
try {
|
|
372
|
+
configure({ apiKey: opts.apiKey, region: opts.region, baseUrl: opts.baseUrl });
|
|
373
|
+
} catch (err) {
|
|
374
|
+
fail(err);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
function fail(err) {
|
|
378
|
+
if (err instanceof ArchilApiError) {
|
|
379
|
+
console.error(`Error (${err.status}): ${err.message}`);
|
|
380
|
+
} else {
|
|
381
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
382
|
+
}
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
function isDiskId(s) {
|
|
386
|
+
return /^(dsk-|fs-)[0-9a-fA-F]+$/.test(s);
|
|
387
|
+
}
|
|
388
|
+
async function resolveDisk(idOrName) {
|
|
389
|
+
if (isDiskId(idOrName)) {
|
|
390
|
+
return getDisk(idOrName);
|
|
391
|
+
}
|
|
392
|
+
const matches = await listDisks({ name: idOrName });
|
|
393
|
+
if (matches.length === 0) {
|
|
394
|
+
throw new ArchilApiError(`No disk found with name '${idOrName}'`, 404);
|
|
395
|
+
}
|
|
396
|
+
if (matches.length > 1) {
|
|
397
|
+
throw new ArchilApiError(
|
|
398
|
+
`Multiple disks match name '${idOrName}' \u2014 pass a disk id (dsk-...) instead`,
|
|
399
|
+
400
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
return matches[0];
|
|
403
|
+
}
|
|
404
|
+
function formatBytes(n) {
|
|
405
|
+
if (n === void 0 || n === null) return void 0;
|
|
406
|
+
if (n < 1024) return `${n} B`;
|
|
407
|
+
const units = ["KB", "MB", "GB", "TB", "PB"];
|
|
408
|
+
let v = n / 1024;
|
|
409
|
+
let i = 0;
|
|
410
|
+
while (v >= 1024 && i < units.length - 1) {
|
|
411
|
+
v /= 1024;
|
|
412
|
+
i++;
|
|
413
|
+
}
|
|
414
|
+
return `${v.toFixed(v >= 100 ? 0 : v >= 10 ? 1 : 2)} ${units[i]}`;
|
|
415
|
+
}
|
|
416
|
+
function renderTable(rows, headers) {
|
|
417
|
+
const all = headers ? [headers, ...rows] : rows;
|
|
418
|
+
if (all.length === 0) return "";
|
|
419
|
+
const cols = Math.max(...all.map((r) => r.length));
|
|
420
|
+
const widths = [];
|
|
421
|
+
for (let i = 0; i < cols; i++) {
|
|
422
|
+
widths[i] = Math.max(...all.map((r) => (r[i] ?? "").length));
|
|
423
|
+
}
|
|
424
|
+
const bar = (l, m, r) => l + widths.map((w) => "\u2500".repeat(w + 2)).join(m) + r;
|
|
425
|
+
const line = (r) => "\u2502 " + widths.map((w, i) => (r[i] ?? "").padEnd(w)).join(" \u2502 ") + " \u2502";
|
|
426
|
+
const out = [bar("\u256D", "\u252C", "\u256E")];
|
|
427
|
+
if (headers) {
|
|
428
|
+
out.push(line(headers));
|
|
429
|
+
out.push(bar("\u251C", "\u253C", "\u2524"));
|
|
430
|
+
}
|
|
431
|
+
for (const r of rows) out.push(line(r));
|
|
432
|
+
out.push(bar("\u2570", "\u2534", "\u256F"));
|
|
433
|
+
return out.join("\n");
|
|
434
|
+
}
|
|
435
|
+
function section(title, body) {
|
|
436
|
+
console.log("");
|
|
437
|
+
console.log(title);
|
|
438
|
+
console.log(body);
|
|
439
|
+
}
|
|
440
|
+
function printDisk(d) {
|
|
441
|
+
console.log(`${d.organization}/${d.name} (${d.id})`);
|
|
442
|
+
const kv = [
|
|
443
|
+
["status", d.status],
|
|
444
|
+
["provider", d.provider],
|
|
445
|
+
["region", d.region],
|
|
446
|
+
["created", d.createdAt],
|
|
447
|
+
["last accessed", d.lastAccessed],
|
|
448
|
+
["data size", formatBytes(d.dataSize)],
|
|
449
|
+
["monthly usage", d.monthlyUsage]
|
|
450
|
+
];
|
|
451
|
+
const visible = kv.filter(([, v]) => v !== void 0 && v !== null && v !== "").map(([k, v]) => [k, String(v)]);
|
|
452
|
+
console.log("");
|
|
453
|
+
console.log(renderTable(visible));
|
|
454
|
+
if (d.mounts && d.mounts.length > 0) {
|
|
455
|
+
const rows = d.mounts.map((m) => [
|
|
456
|
+
m.type ?? "?",
|
|
457
|
+
m.name ?? m.path ?? "",
|
|
458
|
+
m.accessMode ?? ""
|
|
459
|
+
]);
|
|
460
|
+
section("Mounts", renderTable(rows, ["type", "location", "mode"]));
|
|
461
|
+
}
|
|
462
|
+
if (d.authorizedUsers && d.authorizedUsers.length > 0) {
|
|
463
|
+
const rows = d.authorizedUsers.map((u) => {
|
|
464
|
+
if (u.type === "token") {
|
|
465
|
+
return ["token", u.nickname ?? "(no name)", u.tokenSuffix ? `-${u.tokenSuffix}` : ""];
|
|
466
|
+
}
|
|
467
|
+
return [u.type ?? "?", u.identifier ?? u.principal ?? "", ""];
|
|
468
|
+
});
|
|
469
|
+
section("Authorized users", renderTable(rows, ["type", "name / identifier", "suffix"]));
|
|
470
|
+
}
|
|
471
|
+
if (d.connectedClients && d.connectedClients.length > 0) {
|
|
472
|
+
const rows = d.connectedClients.map((c) => [
|
|
473
|
+
c.id ?? "",
|
|
474
|
+
c.ipAddress ?? "",
|
|
475
|
+
c.connectedAt ?? ""
|
|
476
|
+
]);
|
|
477
|
+
section("Connected clients", renderTable(rows, ["id", "ip", "connected at"]));
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
program.command("list").description("List disks in the current region").option("--limit <n>", "Maximum number of disks to return", (v) => parseInt(v, 10)).option("-o, --output <format>", "Output format: table | json", "table").action(async (opts) => {
|
|
481
|
+
try {
|
|
482
|
+
const disks = await listDisks({ limit: opts.limit });
|
|
483
|
+
if (opts.output === "json") {
|
|
484
|
+
console.log(JSON.stringify(disks, null, 2));
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
if (disks.length === 0) {
|
|
488
|
+
console.log("No disks found.");
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const rows = disks.map((d) => [d.id, `${d.organization}/${d.name}`, d.status]);
|
|
492
|
+
console.log(renderTable(rows, ["id", "name", "status"]));
|
|
493
|
+
} catch (err) {
|
|
494
|
+
fail(err);
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
program.command("get <id|name>").description("Show details for a disk (accepts a disk id or name)").option("-o, --output <format>", "Output format: table | json", "table").action(async (idOrName, opts) => {
|
|
498
|
+
try {
|
|
499
|
+
const d = await resolveDisk(idOrName);
|
|
500
|
+
if (opts.output === "json") {
|
|
501
|
+
console.log(JSON.stringify(d, null, 2));
|
|
502
|
+
} else {
|
|
503
|
+
printDisk(d);
|
|
504
|
+
}
|
|
505
|
+
} catch (err) {
|
|
506
|
+
fail(err);
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
program.command("create <name>").description("Create a new disk").action(async (name) => {
|
|
510
|
+
try {
|
|
511
|
+
const result = await createDisk({ name });
|
|
512
|
+
console.log(`Created disk ${result.disk.organization}/${result.disk.name}`);
|
|
513
|
+
console.log(` id: ${result.disk.id}`);
|
|
514
|
+
console.log(` status: ${result.disk.status}`);
|
|
515
|
+
if (result.token) {
|
|
516
|
+
console.log("");
|
|
517
|
+
console.log("Disk token (save this \u2014 it cannot be retrieved again):");
|
|
518
|
+
console.log(` ${result.token}`);
|
|
519
|
+
if (result.tokenIdentifier) {
|
|
520
|
+
console.log(` identifier: ${result.tokenIdentifier}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
} catch (err) {
|
|
524
|
+
fail(err);
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
program.command("delete <id|name>").description("Delete a disk (accepts a disk id or name)").action(async (idOrName) => {
|
|
528
|
+
try {
|
|
529
|
+
const d = await resolveDisk(idOrName);
|
|
530
|
+
await d.delete();
|
|
531
|
+
console.log(`Deleted ${d.organization}/${d.name} (${d.id})`);
|
|
532
|
+
} catch (err) {
|
|
533
|
+
fail(err);
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
program.command("exec <id|name> <command...>").description("Run a command in a container with the disk mounted, return stdout/stderr/exit code (accepts a disk id or name)").action(async (idOrName, cmd) => {
|
|
537
|
+
try {
|
|
538
|
+
const d = await resolveDisk(idOrName);
|
|
539
|
+
const result = await d.exec(cmd.join(" "));
|
|
540
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
541
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
542
|
+
process.exit(result.exitCode);
|
|
543
|
+
} catch (err) {
|
|
544
|
+
fail(err);
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
var keys = program.command("api-keys").description("Manage Archil API keys (account-level credentials)");
|
|
548
|
+
keys.command("list").description("List API keys").option("--limit <n>", "Maximum number of keys to return", (v) => parseInt(v, 10)).action(async (opts) => {
|
|
549
|
+
try {
|
|
550
|
+
const ks = await listApiKeys({ limit: opts.limit });
|
|
551
|
+
if (ks.length === 0) {
|
|
552
|
+
console.log("No API keys found.");
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
for (const k of ks) {
|
|
556
|
+
console.log(`${k.id ?? "-"} ${k.name ?? "-"} ${k.createdAt ?? ""}`);
|
|
557
|
+
}
|
|
558
|
+
} catch (err) {
|
|
559
|
+
fail(err);
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
keys.command("create <name>").description("Create a new API key (the key value is shown once)").option("--description <description>", "Optional description").action(async (name, opts) => {
|
|
563
|
+
try {
|
|
564
|
+
const k = await createApiKey({ name, description: opts.description });
|
|
565
|
+
console.log(`Created API key ${name} (${k.id ?? "?"})`);
|
|
566
|
+
if (k.token) {
|
|
567
|
+
console.log("");
|
|
568
|
+
console.log("API key value (save this \u2014 it cannot be retrieved again):");
|
|
569
|
+
console.log(` ${k.token}`);
|
|
570
|
+
}
|
|
571
|
+
} catch (err) {
|
|
572
|
+
fail(err);
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
keys.command("delete <id>").description("Delete an API key").action(async (id) => {
|
|
576
|
+
try {
|
|
577
|
+
await deleteApiKey(id);
|
|
578
|
+
console.log(`Deleted API key ${id}`);
|
|
579
|
+
} catch (err) {
|
|
580
|
+
fail(err);
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
function rewriteSugar(argv) {
|
|
584
|
+
const known = /* @__PURE__ */ new Set(["list", "get", "create", "delete", "exec", "api-keys", "help", "--help", "-h", "--version", "-V"]);
|
|
585
|
+
const head = argv[2];
|
|
586
|
+
const next = argv[3];
|
|
587
|
+
if (head && next === "exec" && !known.has(head) && !head.startsWith("-")) {
|
|
588
|
+
return [...argv.slice(0, 2), "exec", head, ...argv.slice(4)];
|
|
589
|
+
}
|
|
590
|
+
return argv;
|
|
591
|
+
}
|
|
592
|
+
program.parseAsync(rewriteSugar(process.argv));
|