Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 12 additions & 61 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"@commander-js/extra-typings": "^12.1.0",
"@inkjs/ui": "^2.0.0",
"@inquirer/prompts": "^5.1.2",
"@sfcompute/nodes-sdk-alpha": "0.1.0-alpha.22",
"@sfcompute/nodes-sdk-alpha": "0.1.0-alpha.27",
"@types/ms": "^0.7.34",
"async-retry": "^1.3.3",
"axios": "^1.8.4",
Expand All @@ -20,7 +20,7 @@
"cli-table3": "0.6.5",
"commander": "^12.1.0",
"date-fns": "^4.1.0",
"dayjs": "^1.11.13",
"dayjs": "^1.11.19",
"dotenv": "^16.4.5",
"ink": "^5.2.0",
"ink-link": "^4.1.0",
Expand Down
62 changes: 39 additions & 23 deletions src/lib/nodes/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
pluralizeNodes,
startOrNowOption,
yesOption,
zoneOption,
} from "./utils.ts";
import { handleNodesError, nodesClient } from "../../nodesClient.ts";
import { logAndQuit } from "../../helpers/errors.ts";
Expand Down Expand Up @@ -82,7 +81,18 @@ const create = new Command("create")
"[Required: names or --count] Number of nodes to create with auto-generated names",
validateCount,
)
.addOption(zoneOption)
.addOption(
new Option(
"-z, --zone <zone>",
"[Required: zone or --any-zone if --auto is provided] Zone for your nodes",
).conflicts("any-zone"),
)
.addOption(
new Option(
"--any-zone",
"Use any zone that meets requirements",
).conflicts("zone"),
)
.addOption(maxPriceOption)
.addOption(
new Option(
Expand Down Expand Up @@ -125,41 +135,42 @@ const create = new Command("create")
.addOption(jsonOption)
.hook("preAction", (command) => {
const names = command.args;
const { count, start, duration, end, auto, reserved } = command
.opts();
const { count, start, duration, end, auto, reserved, anyZone, zone } =
command
.opts();

// Validate arguments
if (names.length === 0 && !count) {
console.error(
command.error(
red("Must specify either node names or use \`--count\` option\n"),
);
command.help();
process.exit(1);
}

if (names.length > 0 && count) {
if (names.length !== count) {
console.error(red(
command.error(red(
`You specified ${names.length} ${
names.length === 1 ? "node name" : "node names"
} but \`--count\` is set to ${count}. The number of names must match the \`count\`.\n`,
));
command.help();
process.exit(1);
}
}

if (auto && !anyZone && !zone) {
command.error(red(
"If --auto is provided, you must specify a zone or use --any-zone\n",
));
}

if (reserved && auto) {
console.error(red("Specify either --reserved or --auto, but not both\n"));
command.help();
process.exit(1);
command.error(red(
"Specify either --reserved or --auto, but not both\n",
));
}

// Validate duration/end like buy command
if (typeof end !== "undefined" && typeof duration !== "undefined") {
console.error(red("Specify either --duration or --end, but not both\n"));
command.help();
process.exit(1);
command.error(red("Specify either --duration or --end, but not both\n"));
}

// Validate that timing flags are only used with reserved nodes
Expand All @@ -168,25 +179,21 @@ const create = new Command("create")
(start !== "NOW" || typeof duration !== "undefined" ||
typeof end !== "undefined")
) {
console.error(
command.error(
red(
"Auto-reserved nodes start immediately and cannot have a start time, duration, or end time.\n",
),
);
command.help();
process.exit(1);
}

if (
!auto && typeof duration === "undefined" && typeof end === "undefined"
) {
console.error(
command.error(
red(
"You must specify either --duration or --end to create a reserved node.\n",
),
);
command.help();
process.exit(1);
}
})
.addHelpText(
Expand Down Expand Up @@ -250,6 +257,7 @@ async function createNodesAction(
desired_count: count,
max_price_per_node_hour: options.maxPrice * 100,
names: names.length > 0 ? names : undefined,
any_zone: options.anyZone ?? false,
zone: options.zone,
cloud_init_user_data: encodedUserData,
image_id: options.image,
Expand Down Expand Up @@ -393,10 +401,13 @@ async function createNodesAction(
confirmationMessage += ` for ~$${
pricePerNodeHour.toFixed(2)
}/node/hr`;
if ("zone" in quote) {
confirmationMessage += ` on ${cyan(quote.zone)}`;
}
} else {
logAndQuit(
red(
"No nodes available matching your requirements. This is likely due to insufficient capacity.",
"No capacity available matching your hardware and pricing requirements. You can view zone capacity at https://sfcompute.com/dashboard/zones.",
),
);
}
Expand All @@ -405,6 +416,11 @@ async function createNodesAction(
confirmationMessage += ` for up to $${
options.maxPrice.toFixed(2)
}/node/hr`;
if (options.zone) {
confirmationMessage += ` on ${cyan(options.zone)}`;
} else {
confirmationMessage += ` on any matching zone`;
}
}

// Add node names at the end after a colon
Expand Down
7 changes: 6 additions & 1 deletion src/lib/nodes/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,12 @@ function NodeVerboseDisplay({ node }: { node: SFCNodes.Node }) {
<Row head="Type: " value={printNodeType(node.node_type)} />
<Row head="Status: " value={getStatusColor(node.status)} />
<Row head="GPU: " value={node.gpu_type} />
<Row head="Zone: " value={node.zone ?? "Not specified"} />
<Row
head="Zone: "
value={node.zone ?? node.node_type === "autoreserved"
? "Any matching zone"
: "Not specified"}
/>
<Row head="Owner: " value={node.owner} />
</Box>

Expand Down
10 changes: 1 addition & 9 deletions src/lib/nodes/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export function createNodesTable(
getStatusColor(node.status),
lastVm?.id ?? "",
node.gpu_type,
node.zone || "N/A",
node.zone || (node.node_type === "autoreserved" ? "Any matching" : "N/A"),
startEnd,
maxPrice,
]);
Expand Down Expand Up @@ -277,14 +277,6 @@ export const yesOption = new Option(
"Skip confirmation prompt",
);

/**
* Common --zone option for zone selection
*/
export const zoneOption = new Option(
"-z, --zone <zone>",
"[Required] Zone for your nodes",
).makeOptionMandatory();

/**
* Common --max-price option for nodes commands
*/
Expand Down