@ornncompute/cli 0.2.9 → 0.2.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/package.json +1 -1
- package/vendor/capabilities/capabilities/listings.d.ts +5 -0
- package/vendor/capabilities/capabilities/listings.js +167 -5
- package/vendor/capabilities/capabilities/staff-access.d.ts +3 -1
- package/vendor/capabilities/capabilities/staff-access.js +100 -28
- package/vendor/capabilities/capabilities/staff-commerce.d.ts +91 -14
- package/vendor/capabilities/capabilities/staff-commerce.js +466 -67
- package/vendor/capabilities/capabilities/staff-fleet.d.ts +33 -27
- package/vendor/capabilities/capabilities/staff-fleet.js +143 -44
- package/vendor/capabilities/capabilities/staff-shared.d.ts +29 -0
- package/vendor/capabilities/capabilities/staff-shared.js +64 -0
- package/vendor/capabilities/index.d.ts +2 -2
- package/vendor/capabilities/index.js +2 -2
package/package.json
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export declare const listingCreateInput: z.ZodObject<{
|
|
3
|
+
operatorId: z.ZodOptional<z.ZodString>;
|
|
4
|
+
operator_id: z.ZodOptional<z.ZodString>;
|
|
5
|
+
facilityId: z.ZodOptional<z.ZodString>;
|
|
6
|
+
facility_id: z.ZodOptional<z.ZodString>;
|
|
3
7
|
kind: z.ZodOptional<z.ZodEnum<{
|
|
4
8
|
link: "link";
|
|
5
9
|
gpu: "gpu";
|
|
@@ -36,6 +40,7 @@ export declare const listingCreateInput: z.ZodObject<{
|
|
|
36
40
|
price_omitted: z.ZodOptional<z.ZodBoolean>;
|
|
37
41
|
announce: z.ZodOptional<z.ZodBoolean>;
|
|
38
42
|
text: z.ZodOptional<z.ZodString>;
|
|
43
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
39
44
|
}, z.core.$strip>;
|
|
40
45
|
export declare const listCatalogTermsCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
41
46
|
export declare const listListingsCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
@@ -2,6 +2,10 @@ import { z } from "zod";
|
|
|
2
2
|
import { defineCapability, preview } from "../capability.js";
|
|
3
3
|
import { bindHardwareFields, HARDWARE_LISTING_FIELDS, HARDWARE_LISTING_FILTERS, loadHardwareCatalog, projectCatalogTerms, } from "../spec-catalog.js";
|
|
4
4
|
export const listingCreateInput = z.object({
|
|
5
|
+
operatorId: z.string().optional(),
|
|
6
|
+
operator_id: z.string().optional(),
|
|
7
|
+
facilityId: z.string().optional(),
|
|
8
|
+
facility_id: z.string().optional(),
|
|
5
9
|
kind: z.enum(["gpu", "cpu", "node", "link"]).optional(),
|
|
6
10
|
gpu: z.string().optional(),
|
|
7
11
|
cpu: z.string().optional(),
|
|
@@ -23,16 +27,23 @@ export const listingCreateInput = z.object({
|
|
|
23
27
|
storage: z.string().optional(),
|
|
24
28
|
internet: z.string().optional(),
|
|
25
29
|
network_hardware: z.string().optional(),
|
|
26
|
-
available_start_at: z
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
available_start_at: z
|
|
31
|
+
.string()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("Start of the availability window. YYYY-MM-DD or RFC3339."),
|
|
34
|
+
available_end_at: z
|
|
35
|
+
.string()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("End of the availability window. YYYY-MM-DD or RFC3339."),
|
|
38
|
+
available_from: z.string().optional().describe("Alias for available_start_at."),
|
|
39
|
+
available_to: z.string().optional().describe("Alias for available_end_at."),
|
|
30
40
|
buy_now_price_per_gpu_hour: z.number().optional(),
|
|
31
41
|
cost_per_gpu_hour: z.number().optional(),
|
|
32
42
|
deposit_percent: z.number().optional(),
|
|
33
43
|
price_omitted: z.boolean().optional(),
|
|
34
44
|
announce: z.boolean().optional(),
|
|
35
45
|
text: z.string().optional(),
|
|
46
|
+
confirm: z.boolean().optional(),
|
|
36
47
|
});
|
|
37
48
|
const catalogTermsInput = z.object({ kind: z.enum(["gpu", "cpu", "node", "link"]).optional() });
|
|
38
49
|
const slackListingCreateParams = {
|
|
@@ -124,6 +135,38 @@ export const listListingsCapability = defineCapability({
|
|
|
124
135
|
return ctx.fetch("/listings/available", { method: "GET", body: bound.payload });
|
|
125
136
|
},
|
|
126
137
|
});
|
|
138
|
+
/** Commerce parses listing windows as RFC3339 timestamps, not bare dates. */
|
|
139
|
+
function rfc3339(value) {
|
|
140
|
+
const trimmed = value.trim();
|
|
141
|
+
if (!trimmed.includes("T"))
|
|
142
|
+
return `${trimmed}T00:00:00Z`;
|
|
143
|
+
return trimmed.endsWith("Z") || /[+-]\d{2}:\d{2}$/.test(trimmed) ? trimmed : `${trimmed}Z`;
|
|
144
|
+
}
|
|
145
|
+
// Keys the schema accepts for the caller's convenience but that are never part
|
|
146
|
+
// of the create body: window aliases (resolved into startAt/endAt below), the
|
|
147
|
+
// browse-side filters this input shares with listings.available, and the
|
|
148
|
+
// confirm flag. They used to ride along into the POST verbatim, which is why
|
|
149
|
+
// naming a window with available_from/available_to looked like a dead
|
|
150
|
+
// parameter — the alias was passed through unresolved and unnormalized instead
|
|
151
|
+
// of becoming the startAt commerce actually reads.
|
|
152
|
+
const LISTING_CREATE_INPUT_ONLY_KEYS = [
|
|
153
|
+
"available_start_at",
|
|
154
|
+
"available_end_at",
|
|
155
|
+
"available_from",
|
|
156
|
+
"available_to",
|
|
157
|
+
"starts_after",
|
|
158
|
+
"starts_before",
|
|
159
|
+
"ends_after",
|
|
160
|
+
"ends_before",
|
|
161
|
+
"min_nodes",
|
|
162
|
+
"site",
|
|
163
|
+
"kind",
|
|
164
|
+
"confirm",
|
|
165
|
+
"operatorId",
|
|
166
|
+
"operator_id",
|
|
167
|
+
"facilityId",
|
|
168
|
+
"facility_id",
|
|
169
|
+
];
|
|
127
170
|
export const createListingCapability = defineCapability({
|
|
128
171
|
id: "listings.create",
|
|
129
172
|
domain: "listings",
|
|
@@ -145,7 +188,126 @@ export const createListingCapability = defineCapability({
|
|
|
145
188
|
const bound = bindHardwareFields(hardware.terms, normalized, HARDWARE_LISTING_FIELDS);
|
|
146
189
|
if (!bound.ok)
|
|
147
190
|
return bound;
|
|
148
|
-
|
|
191
|
+
let operatorId = parsed.operatorId ?? parsed.operator_id;
|
|
192
|
+
let facilityId = parsed.facilityId ?? parsed.facility_id;
|
|
193
|
+
if (ctx.confirmed || parsed.site_operator || parsed.site_nickname || parsed.site) {
|
|
194
|
+
if (!operatorId) {
|
|
195
|
+
try {
|
|
196
|
+
const opsResp = (await ctx.fetch("/provisioning/operators", { method: "GET" }));
|
|
197
|
+
const ops = opsResp?.operators ?? [];
|
|
198
|
+
const query = (parsed.site_operator ?? parsed.site ?? "").trim().toLowerCase();
|
|
199
|
+
if (query) {
|
|
200
|
+
const match = ops.find((o) => o.operatorId.toLowerCase() === query ||
|
|
201
|
+
o.slug.toLowerCase() === query ||
|
|
202
|
+
o.displayName.toLowerCase() === query);
|
|
203
|
+
if (match)
|
|
204
|
+
operatorId = match.operatorId;
|
|
205
|
+
}
|
|
206
|
+
if (!operatorId && ops.length > 0) {
|
|
207
|
+
operatorId = ops[0].operatorId;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// Ignore fetch failure (e.g. in unit tests)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (!facilityId) {
|
|
215
|
+
try {
|
|
216
|
+
const facsResp = (await ctx.fetch("/provisioning/facilities", { method: "GET" }));
|
|
217
|
+
const facs = facsResp?.facilities ?? [];
|
|
218
|
+
const query = (parsed.site_nickname ?? parsed.site ?? "").trim().toLowerCase();
|
|
219
|
+
if (query) {
|
|
220
|
+
const match = facs.find((f) => f.facilityId.toLowerCase() === query ||
|
|
221
|
+
f.slug.toLowerCase() === query ||
|
|
222
|
+
f.displayName.toLowerCase() === query);
|
|
223
|
+
if (match)
|
|
224
|
+
facilityId = match.facilityId;
|
|
225
|
+
}
|
|
226
|
+
if (!facilityId && operatorId) {
|
|
227
|
+
const match = facs.find((f) => f.operatorId === operatorId);
|
|
228
|
+
if (match)
|
|
229
|
+
facilityId = match.facilityId;
|
|
230
|
+
}
|
|
231
|
+
if (!facilityId && facs.length > 0) {
|
|
232
|
+
facilityId = facs[0].facilityId;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// Ignore fetch failure
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// Every window alias resolves to the same pair, and both are normalized:
|
|
241
|
+
// commerce decodes startAt/endAt as time.Time, so a bare "2026-09-01"
|
|
242
|
+
// fails the whole request body rather than the one field.
|
|
243
|
+
const startAt = rfc3339(parsed.available_start_at ??
|
|
244
|
+
parsed.available_from ??
|
|
245
|
+
parsed.starts_after ??
|
|
246
|
+
new Date().toISOString());
|
|
247
|
+
// `ends_before` is the window's closing bound and the counterpart of the
|
|
248
|
+
// `starts_after` the opening bound already accepts. Only `ends_after` was
|
|
249
|
+
// consulted here, so naming an end with `ends_before` was silently dropped
|
|
250
|
+
// and endAt fell back to the +30d default.
|
|
251
|
+
const endAt = rfc3339(parsed.available_end_at ??
|
|
252
|
+
parsed.available_to ??
|
|
253
|
+
parsed.ends_before ??
|
|
254
|
+
parsed.ends_after ??
|
|
255
|
+
new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString());
|
|
256
|
+
const capacityNodes = parsed.node_count ?? parsed.min_nodes ?? 1;
|
|
257
|
+
const gpusPerNode = parsed.gpus_per_node ?? 8;
|
|
258
|
+
const buyNowPrice = parsed.buy_now_price_per_gpu_hour;
|
|
259
|
+
const costPrice = parsed.cost_per_gpu_hour;
|
|
260
|
+
// `deposit_percent` and `price_omitted` were both declared on the schema and
|
|
261
|
+
// then never read, so they rode into the body under their own names and
|
|
262
|
+
// commerce ignored them: a deposit plan never took, and a listing whose
|
|
263
|
+
// source quoted no price still got the invented 1.00 floor below.
|
|
264
|
+
const depositPercent = parsed.deposit_percent;
|
|
265
|
+
const priceOmitted = parsed.price_omitted === true;
|
|
266
|
+
const floorRate = costPrice != null ? String(costPrice) : priceOmitted ? null : "1.00";
|
|
267
|
+
const { buy_now_price_per_gpu_hour: _, cost_per_gpu_hour: __, deposit_percent: ___, price_omitted: ____, ...boundPayload } = bound.payload;
|
|
268
|
+
const cleanPayload = Object.fromEntries(Object.entries(boundPayload).filter(([key]) => !LISTING_CREATE_INPUT_ONLY_KEYS.includes(key)));
|
|
269
|
+
const payload = {
|
|
270
|
+
backing_mode: "forward",
|
|
271
|
+
...cleanPayload,
|
|
272
|
+
...(operatorId ? { operatorId, operator_id: operatorId } : {}),
|
|
273
|
+
...(facilityId ? { facilityId, facility_id: facilityId } : {}),
|
|
274
|
+
...(operatorId
|
|
275
|
+
? {
|
|
276
|
+
startAt,
|
|
277
|
+
start_at: startAt,
|
|
278
|
+
endAt,
|
|
279
|
+
end_at: endAt,
|
|
280
|
+
capacityNodes,
|
|
281
|
+
capacity_nodes: capacityNodes,
|
|
282
|
+
gpusPerNode,
|
|
283
|
+
gpus_per_node: gpusPerNode,
|
|
284
|
+
currency: "USD",
|
|
285
|
+
buyNowEnabled: buyNowPrice != null,
|
|
286
|
+
buy_now_enabled: buyNowPrice != null,
|
|
287
|
+
...(buyNowPrice != null
|
|
288
|
+
? {
|
|
289
|
+
buyNowRatePerGpuHour: String(buyNowPrice),
|
|
290
|
+
buy_now_rate_per_gpu_hour: String(buyNowPrice),
|
|
291
|
+
}
|
|
292
|
+
: {}),
|
|
293
|
+
...(depositPercent != null
|
|
294
|
+
? {
|
|
295
|
+
allowsDepositPlan: true,
|
|
296
|
+
allows_deposit_plan: true,
|
|
297
|
+
downPaymentPct: String(depositPercent),
|
|
298
|
+
down_payment_pct: String(depositPercent),
|
|
299
|
+
}
|
|
300
|
+
: {}),
|
|
301
|
+
bidEnabled: true,
|
|
302
|
+
bid_enabled: true,
|
|
303
|
+
// Commerce takes a null floor rate; only a stated cost becomes one.
|
|
304
|
+
// The old 1.00 fallback published a floor nobody had quoted.
|
|
305
|
+
...(floorRate != null
|
|
306
|
+
? { floorRatePerGpuHour: floorRate, floor_rate_per_gpu_hour: floorRate }
|
|
307
|
+
: {}),
|
|
308
|
+
}
|
|
309
|
+
: {}),
|
|
310
|
+
};
|
|
149
311
|
if (!ctx.confirmed) {
|
|
150
312
|
return preview("create_listing", payload);
|
|
151
313
|
}
|
|
@@ -13,8 +13,10 @@ export declare const sshKeyAddInput: z.ZodObject<{
|
|
|
13
13
|
reservationId: z.ZodString;
|
|
14
14
|
tenantId: z.ZodString;
|
|
15
15
|
authUserId: z.ZodString;
|
|
16
|
-
publicKey: z.ZodString
|
|
16
|
+
publicKey: z.ZodOptional<z.ZodString>;
|
|
17
17
|
label: z.ZodOptional<z.ZodString>;
|
|
18
|
+
sshKeyId: z.ZodOptional<z.ZodString>;
|
|
19
|
+
push: z.ZodOptional<z.ZodBoolean>;
|
|
18
20
|
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
19
21
|
}, z.core.$strip>;
|
|
20
22
|
export declare const sshKeyAddCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
@@ -17,6 +17,16 @@ import { mapBody, seg, statusOf } from "./staff-shared.js";
|
|
|
17
17
|
// `assertReservationOwnedByTenantUser` below so every surface runs them.
|
|
18
18
|
/** Acts as the caller for a write the tenant's own user is nominally making. */
|
|
19
19
|
const AUTH_USER_HEADER = "X-Auth-User-Id";
|
|
20
|
+
/** Live keys only: a revoked or inactive key must never reach a machine. */
|
|
21
|
+
function activePublicKeys(keys, wanted) {
|
|
22
|
+
return keys
|
|
23
|
+
.filter((key) => key.id &&
|
|
24
|
+
wanted(key.id) &&
|
|
25
|
+
key.status === "active" &&
|
|
26
|
+
!key.revoked_at &&
|
|
27
|
+
Boolean(key.public_key?.trim()))
|
|
28
|
+
.map((key) => key.public_key.trim());
|
|
29
|
+
}
|
|
20
30
|
async function assertReservationOwnedByTenantUser(ctx, options) {
|
|
21
31
|
let reservation;
|
|
22
32
|
try {
|
|
@@ -52,6 +62,45 @@ async function assertMachineBelongsToReservation(ctx, options) {
|
|
|
52
62
|
throw new Error("Machine does not belong to that reservation.");
|
|
53
63
|
}
|
|
54
64
|
}
|
|
65
|
+
/** A VM's keys live on its pod; a bare-metal node's live on the node itself. */
|
|
66
|
+
async function keyTargetPath(ctx, instanceId) {
|
|
67
|
+
try {
|
|
68
|
+
const pod = (await ctx.fetch(`/internal/pods/${seg(instanceId)}`, { method: "GET" }));
|
|
69
|
+
if (pod.kind === "virtual-machine") {
|
|
70
|
+
return `/internal/pods/${seg(instanceId)}/keys`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// instanceId is a host node
|
|
75
|
+
}
|
|
76
|
+
return `/internal/nodes/${seg(instanceId)}/keys`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Push every key currently attached to a reservation onto its machines.
|
|
80
|
+
*
|
|
81
|
+
* Mirrors the staff web workflow (gateway `sync_reservation_keys`). Attaching
|
|
82
|
+
* a key in commerce only records the grant; without this the key is invisible
|
|
83
|
+
* on the machine until someone runs ssh_key_push by hand.
|
|
84
|
+
*/
|
|
85
|
+
async function syncReservationKeys(ctx, options) {
|
|
86
|
+
const attached = (await ctx.fetch(`/internal/v1/reservations/${seg(options.reservationId)}/ssh-keys`, { method: "GET" }));
|
|
87
|
+
const selected = new Set((attached.sshKeys ?? []).map((row) => row.sshKeyId ?? row.ssh_key_id).filter(Boolean));
|
|
88
|
+
const listed = (await ctx.fetch(`/v1/organizations/${seg(options.tenantId)}/ssh-keys`, {
|
|
89
|
+
method: "GET",
|
|
90
|
+
}));
|
|
91
|
+
const sshAuthorizedKeys = activePublicKeys(listed.ssh_keys ?? [], (id) => selected.has(id));
|
|
92
|
+
const listedNodes = (await ctx.fetch(`/internal/nodes?reservation_id=${seg(options.reservationId)}`, { method: "GET" }));
|
|
93
|
+
const instanceIds = (listedNodes.nodes ?? [])
|
|
94
|
+
.map((node) => node.node_id ?? node.id)
|
|
95
|
+
.filter((id) => Boolean(id));
|
|
96
|
+
for (const instanceId of instanceIds) {
|
|
97
|
+
await ctx.fetch(await keyTargetPath(ctx, instanceId), {
|
|
98
|
+
method: "PATCH",
|
|
99
|
+
body: { ssh_authorized_keys: sshAuthorizedKeys },
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return { machinesSynced: instanceIds.length, keysApplied: sshAuthorizedKeys.length };
|
|
103
|
+
}
|
|
55
104
|
export const sshKeyPushInput = z.object({
|
|
56
105
|
instanceId: z.string(),
|
|
57
106
|
reservationId: z.string(),
|
|
@@ -95,29 +144,12 @@ export const sshKeyPushCapability = defineStaffMcpCapability({
|
|
|
95
144
|
method: "GET",
|
|
96
145
|
}));
|
|
97
146
|
const wanted = new Set(parsed.sshKeyIds);
|
|
98
|
-
const sshAuthorizedKeys = (listed.ssh_keys ?? [])
|
|
99
|
-
.filter((key) => key.id &&
|
|
100
|
-
wanted.has(key.id) &&
|
|
101
|
-
key.status === "active" &&
|
|
102
|
-
!key.revoked_at &&
|
|
103
|
-
Boolean(key.public_key?.trim()))
|
|
104
|
-
.map((key) => key.public_key.trim());
|
|
147
|
+
const sshAuthorizedKeys = activePublicKeys(listed.ssh_keys ?? [], (id) => wanted.has(id));
|
|
105
148
|
const body = {
|
|
106
149
|
ssh_authorized_keys: sshAuthorizedKeys,
|
|
107
150
|
...mapBody(parsed, { requestId: "request_id" }),
|
|
108
151
|
};
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
const pod = (await ctx.fetch(`/internal/pods/${seg(parsed.instanceId)}`, {
|
|
112
|
-
method: "GET",
|
|
113
|
-
}));
|
|
114
|
-
if (pod.kind === "virtual-machine") {
|
|
115
|
-
keysPath = `/internal/pods/${seg(parsed.instanceId)}/keys`;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
// instanceId is a host node
|
|
120
|
-
}
|
|
152
|
+
const keysPath = await keyTargetPath(ctx, parsed.instanceId);
|
|
121
153
|
return ctx.fetch(keysPath, {
|
|
122
154
|
method: "PATCH",
|
|
123
155
|
body,
|
|
@@ -129,37 +161,77 @@ export const sshKeyAddInput = z.object({
|
|
|
129
161
|
reservationId: z.string(),
|
|
130
162
|
tenantId: z.string(),
|
|
131
163
|
authUserId: z.string(),
|
|
132
|
-
publicKey: z.string().min(1),
|
|
164
|
+
publicKey: z.string().min(1).optional(),
|
|
133
165
|
label: z.string().optional(),
|
|
166
|
+
sshKeyId: z.string().optional(),
|
|
167
|
+
push: z.boolean().optional(),
|
|
134
168
|
confirm: z.boolean().optional(),
|
|
135
169
|
});
|
|
136
170
|
export const sshKeyAddCapability = defineStaffMcpCapability({
|
|
137
171
|
id: "ssh-key.add",
|
|
138
172
|
domain: "access",
|
|
139
173
|
roles: ["admin"],
|
|
140
|
-
description: "Internal staff only:
|
|
174
|
+
description: "Internal staff only: attach an SSH key to a reservation for a tenant user. " +
|
|
175
|
+
"Pass sshKeyId to attach a key the organization already has, or publicKey (with label) " +
|
|
176
|
+
"to register a new organization key and attach it. The reservation's machines are resynced " +
|
|
177
|
+
"afterwards so the key works immediately; pass push: false to attach without touching them. " +
|
|
178
|
+
"Requires confirm: true.",
|
|
141
179
|
mutation: "preview-confirm",
|
|
142
180
|
http: { method: "POST", path: "/internal/v1/reservations/{reservationId}/ssh-keys" },
|
|
143
181
|
input: sshKeyAddInput,
|
|
144
182
|
execute: async (ctx, input) => {
|
|
145
183
|
const parsed = sshKeyAddInput.parse(input);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
};
|
|
184
|
+
if (!parsed.sshKeyId && !parsed.publicKey) {
|
|
185
|
+
throw new Error("Pass sshKeyId to attach an existing organization key, or publicKey to register a new one.");
|
|
186
|
+
}
|
|
150
187
|
if (!ctx.confirmed) {
|
|
151
|
-
return preview("ssh-key.add", {
|
|
188
|
+
return preview("ssh-key.add", {
|
|
189
|
+
reservationId: parsed.reservationId,
|
|
190
|
+
...(parsed.sshKeyId ? { sshKeyId: parsed.sshKeyId } : { publicKey: parsed.publicKey, label: parsed.label }),
|
|
191
|
+
});
|
|
152
192
|
}
|
|
153
193
|
await assertReservationOwnedByTenantUser(ctx, {
|
|
154
194
|
reservationId: parsed.reservationId,
|
|
155
195
|
tenantId: parsed.tenantId,
|
|
156
196
|
authUserId: parsed.authUserId,
|
|
157
197
|
});
|
|
158
|
-
|
|
198
|
+
// Commerce attaches an existing authorization key by id; it has no notion
|
|
199
|
+
// of a public key blob. Registering first mirrors the staff web workflow
|
|
200
|
+
// (gateway `add_reservation_key`), which this tool previously bypassed by
|
|
201
|
+
// posting `public_key` into a body that only reads `sshKeyId`/`userId`.
|
|
202
|
+
let sshKeyId = parsed.sshKeyId;
|
|
203
|
+
if (!sshKeyId) {
|
|
204
|
+
const registered = (await ctx.fetch(`/v1/organizations/${seg(parsed.tenantId)}/ssh-keys`, {
|
|
205
|
+
method: "POST",
|
|
206
|
+
body: { public_key: parsed.publicKey, ...mapBody(parsed, { label: "label" }) },
|
|
207
|
+
}));
|
|
208
|
+
if (!registered.id) {
|
|
209
|
+
throw new Error("Authorization did not return an SSH key id.");
|
|
210
|
+
}
|
|
211
|
+
sshKeyId = registered.id;
|
|
212
|
+
}
|
|
213
|
+
const attached = await ctx.fetch(`/internal/v1/reservations/${seg(parsed.reservationId)}/ssh-keys`, {
|
|
159
214
|
method: "POST",
|
|
160
|
-
body,
|
|
215
|
+
body: { sshKeyId, userId: parsed.authUserId },
|
|
161
216
|
headers: { [AUTH_USER_HEADER]: parsed.authUserId },
|
|
162
217
|
});
|
|
218
|
+
if (parsed.push === false) {
|
|
219
|
+
return { attached, pushed: false };
|
|
220
|
+
}
|
|
221
|
+
// The attach only records the grant. The staff web workflow resyncs the
|
|
222
|
+
// reservation's machines in the same action, so a key added here is
|
|
223
|
+
// usable immediately rather than only after a separate ssh_key_push.
|
|
224
|
+
try {
|
|
225
|
+
return { attached, pushed: true, ...(await syncReservationKeys(ctx, parsed)) };
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
return {
|
|
229
|
+
attached,
|
|
230
|
+
pushed: false,
|
|
231
|
+
pushError: error instanceof Error ? error.message : String(error),
|
|
232
|
+
detail: "The key is attached to the reservation but was not pushed to its machines. Run ssh_key_push to retry.",
|
|
233
|
+
};
|
|
234
|
+
}
|
|
163
235
|
},
|
|
164
236
|
});
|
|
165
237
|
export const sshKeyRevokeInput = z.object({
|
|
@@ -7,8 +7,6 @@ export declare const reservationCancelCapability: import("../capability.ts").Cap
|
|
|
7
7
|
export declare const reservationTransferInput: z.ZodObject<{
|
|
8
8
|
reservationId: z.ZodString;
|
|
9
9
|
targetTenantId: z.ZodString;
|
|
10
|
-
targetAuthUserId: z.ZodOptional<z.ZodString>;
|
|
11
|
-
nodeId: z.ZodOptional<z.ZodString>;
|
|
12
10
|
reservedStrategy: z.ZodEnum<{
|
|
13
11
|
reject: "reject";
|
|
14
12
|
park: "park";
|
|
@@ -24,10 +22,12 @@ export declare const reservationMachinesListInput: z.ZodObject<{
|
|
|
24
22
|
export declare const reservationMachinesListCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
25
23
|
export declare const reservationSshKeyAddInput: z.ZodObject<{
|
|
26
24
|
reservationId: z.ZodString;
|
|
25
|
+
tenantId: z.ZodString;
|
|
27
26
|
userId: z.ZodString;
|
|
28
|
-
|
|
27
|
+
sshKeyId: z.ZodOptional<z.ZodString>;
|
|
28
|
+
keyId: z.ZodOptional<z.ZodString>;
|
|
29
|
+
publicKey: z.ZodOptional<z.ZodString>;
|
|
29
30
|
label: z.ZodOptional<z.ZodString>;
|
|
30
|
-
nodeId: z.ZodOptional<z.ZodString>;
|
|
31
31
|
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
32
32
|
}, z.core.$strip>;
|
|
33
33
|
export declare const reservationSshKeyAddCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
@@ -35,18 +35,36 @@ export declare const reservationSshKeyRemoveInput: z.ZodObject<{
|
|
|
35
35
|
reservationId: z.ZodString;
|
|
36
36
|
keyId: z.ZodString;
|
|
37
37
|
userId: z.ZodString;
|
|
38
|
-
nodeId: z.ZodOptional<z.ZodString>;
|
|
39
38
|
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
40
39
|
}, z.core.$strip>;
|
|
41
40
|
export declare const reservationSshKeyRemoveCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
42
41
|
export declare const reservationPublishInput: z.ZodObject<{
|
|
43
|
-
reservationId: z.ZodString
|
|
42
|
+
reservationId: z.ZodOptional<z.ZodString>;
|
|
43
|
+
reservation_id: z.ZodOptional<z.ZodString>;
|
|
44
|
+
version: z.ZodOptional<z.ZodNumber>;
|
|
45
|
+
awaitDownPayment: z.ZodOptional<z.ZodBoolean>;
|
|
46
|
+
await_down_payment: z.ZodOptional<z.ZodBoolean>;
|
|
44
47
|
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
45
48
|
}, z.core.$strip>;
|
|
46
49
|
export declare const reservationPublishCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
50
|
+
export declare const reservationShowInput: z.ZodObject<{
|
|
51
|
+
reservationId: z.ZodString;
|
|
52
|
+
}, z.core.$strip>;
|
|
53
|
+
export declare const reservationShowCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
54
|
+
export declare const reservationListInput: z.ZodObject<{
|
|
55
|
+
organizationId: z.ZodOptional<z.ZodString>;
|
|
56
|
+
status: z.ZodOptional<z.ZodString>;
|
|
57
|
+
cursor: z.ZodOptional<z.ZodString>;
|
|
58
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
59
|
+
}, z.core.$strip>;
|
|
60
|
+
export declare const reservationListCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
47
61
|
export declare const bidAcceptInput: z.ZodObject<{
|
|
48
|
-
bidId: z.ZodString
|
|
62
|
+
bidId: z.ZodOptional<z.ZodString>;
|
|
63
|
+
bid_id: z.ZodOptional<z.ZodString>;
|
|
49
64
|
acceptedGpuCount: z.ZodOptional<z.ZodNumber>;
|
|
65
|
+
accepted_gpu_count: z.ZodOptional<z.ZodNumber>;
|
|
66
|
+
fillNodeCount: z.ZodOptional<z.ZodNumber>;
|
|
67
|
+
fill_node_count: z.ZodOptional<z.ZodNumber>;
|
|
50
68
|
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
51
69
|
}, z.core.$strip>;
|
|
52
70
|
export declare const bidAcceptCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
@@ -55,23 +73,28 @@ export declare const bidRejectInput: z.ZodObject<{
|
|
|
55
73
|
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
56
74
|
}, z.core.$strip>;
|
|
57
75
|
export declare const bidRejectCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
76
|
+
export declare const bidShowInput: z.ZodObject<{
|
|
77
|
+
bidId: z.ZodOptional<z.ZodString>;
|
|
78
|
+
bid_id: z.ZodOptional<z.ZodString>;
|
|
79
|
+
}, z.core.$strip>;
|
|
80
|
+
export declare const bidShowCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
58
81
|
export declare const inventoryUpdateInput: z.ZodObject<{
|
|
59
82
|
inventoryId: z.ZodString;
|
|
60
|
-
|
|
61
|
-
|
|
83
|
+
version: z.ZodOptional<z.ZodNumber>;
|
|
84
|
+
operatorId: z.ZodOptional<z.ZodString>;
|
|
85
|
+
facilityId: z.ZodOptional<z.ZodString>;
|
|
62
86
|
gpuType: z.ZodOptional<z.ZodString>;
|
|
63
87
|
nodeCount: z.ZodOptional<z.ZodNumber>;
|
|
64
88
|
gpusPerNode: z.ZodOptional<z.ZodNumber>;
|
|
65
|
-
availableFrom: z.ZodOptional<z.
|
|
66
|
-
availableTo: z.ZodOptional<z.
|
|
67
|
-
availableStartAt: z.ZodOptional<z.
|
|
68
|
-
availableEndAt: z.ZodOptional<z.
|
|
89
|
+
availableFrom: z.ZodOptional<z.ZodString>;
|
|
90
|
+
availableTo: z.ZodOptional<z.ZodString>;
|
|
91
|
+
availableStartAt: z.ZodOptional<z.ZodString>;
|
|
92
|
+
availableEndAt: z.ZodOptional<z.ZodString>;
|
|
69
93
|
cpu: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
70
94
|
ram: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
71
95
|
fabricType: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
72
96
|
storage: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
73
97
|
internet: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
74
|
-
networkHardware: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
75
98
|
buyNowPricePerGpuHour: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
76
99
|
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
77
100
|
}, z.core.$strip>;
|
|
@@ -80,4 +103,58 @@ export declare const tenantCreditProfileShowInput: z.ZodObject<{
|
|
|
80
103
|
tenantId: z.ZodString;
|
|
81
104
|
}, z.core.$strip>;
|
|
82
105
|
export declare const tenantCreditProfileShowCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
106
|
+
export declare const reservationDraftCreateInput: z.ZodObject<{
|
|
107
|
+
organizationId: z.ZodString;
|
|
108
|
+
nodeCount: z.ZodNumber;
|
|
109
|
+
startAt: z.ZodString;
|
|
110
|
+
endAt: z.ZodString;
|
|
111
|
+
billingMode: z.ZodDefault<z.ZodString>;
|
|
112
|
+
pricePerGpuHr: z.ZodString;
|
|
113
|
+
listingId: z.ZodOptional<z.ZodString>;
|
|
114
|
+
accessMode: z.ZodOptional<z.ZodString>;
|
|
115
|
+
notes: z.ZodOptional<z.ZodString>;
|
|
116
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
117
|
+
}, z.core.$strip>;
|
|
118
|
+
export declare const reservationDraftCreateCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
119
|
+
export declare const reservationUpdateInput: z.ZodObject<{
|
|
120
|
+
reservationId: z.ZodString;
|
|
121
|
+
startAt: z.ZodOptional<z.ZodString>;
|
|
122
|
+
endAt: z.ZodOptional<z.ZodString>;
|
|
123
|
+
nodeCount: z.ZodOptional<z.ZodNumber>;
|
|
124
|
+
notes: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
125
|
+
expectedStatus: z.ZodOptional<z.ZodString>;
|
|
126
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
127
|
+
}, z.core.$strip>;
|
|
128
|
+
export declare const reservationUpdateCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
129
|
+
export declare const reservationActivateInput: z.ZodObject<{
|
|
130
|
+
reservationId: z.ZodString;
|
|
131
|
+
version: z.ZodOptional<z.ZodNumber>;
|
|
132
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
133
|
+
}, z.core.$strip>;
|
|
134
|
+
export declare const reservationActivateCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
135
|
+
export declare const reservationCompleteInput: z.ZodObject<{
|
|
136
|
+
reservationId: z.ZodString;
|
|
137
|
+
version: z.ZodOptional<z.ZodNumber>;
|
|
138
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
139
|
+
}, z.core.$strip>;
|
|
140
|
+
export declare const reservationCompleteCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
141
|
+
export declare const inventoryDelistInput: z.ZodObject<{
|
|
142
|
+
inventoryId: z.ZodString;
|
|
143
|
+
version: z.ZodOptional<z.ZodNumber>;
|
|
144
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
145
|
+
}, z.core.$strip>;
|
|
146
|
+
export declare const inventoryDelistCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
147
|
+
export declare const billingInvoicesListInput: z.ZodObject<{
|
|
148
|
+
status: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
|
|
149
|
+
void: "void";
|
|
150
|
+
due: "due";
|
|
151
|
+
open: "open";
|
|
152
|
+
paid: "paid";
|
|
153
|
+
draft: "draft";
|
|
154
|
+
uncollectible: "uncollectible";
|
|
155
|
+
}>>>;
|
|
156
|
+
cursor: z.ZodOptional<z.ZodString>;
|
|
157
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
158
|
+
}, z.core.$strip>;
|
|
159
|
+
export declare const billingInvoicesListCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
83
160
|
export declare const staffCommerceCapabilities: import("../capability.ts").Capability<unknown, unknown>[];
|