Break down jupyter.zig (31KB, 906 lines) into focused modules: - jupyter/mod.zig - Main entry point and command dispatch - jupyter/validation.zig - Security validation functions - jupyter/lifecycle.zig - Service create/start/stop/remove/restore - jupyter/query.zig - List, status, and package queries - jupyter/workspace.zig - Workspace and experiment management Original jupyter.zig now acts as backward-compatible wrapper. Removed 5 unimplemented placeholder functions (~50 lines of dead code). Benefits: - Each module <250 lines (maintainable) - Clear separation of concerns - Easier to test individual components - Better code organization All tests pass.
255 lines
9.3 KiB
Zig
255 lines
9.3 KiB
Zig
const std = @import("std");
|
|
const Config = @import("../../config.zig").Config;
|
|
const ws = @import("../../net/ws/client.zig");
|
|
const crypto = @import("../../utils/crypto.zig");
|
|
const protocol = @import("../../net/protocol.zig");
|
|
|
|
/// List Jupyter services
|
|
pub fn listJupyter(allocator: std.mem.Allocator, args: []const []const u8, json: bool) !void {
|
|
_ = args;
|
|
_ = json;
|
|
try listServices(allocator);
|
|
}
|
|
|
|
/// Show Jupyter service status
|
|
pub fn statusJupyter(allocator: std.mem.Allocator, args: []const []const u8, json: bool) !void {
|
|
_ = args;
|
|
_ = json;
|
|
// Re-use listServices for now as status is part of list
|
|
try listServices(allocator);
|
|
}
|
|
|
|
/// Internal function to list all services
|
|
fn listServices(allocator: std.mem.Allocator) !void {
|
|
const config = try Config.load(allocator);
|
|
defer {
|
|
var mut_config = config;
|
|
mut_config.deinit(allocator);
|
|
}
|
|
|
|
const url = try config.getWebSocketUrl(allocator);
|
|
defer allocator.free(url);
|
|
|
|
var client = ws.Client.connect(allocator, url, config.api_key) catch |err| {
|
|
std.debug.print("Failed to connect to server: {}\n", .{err});
|
|
return;
|
|
};
|
|
defer client.close();
|
|
|
|
const api_key_hash = try crypto.hashApiKey(allocator, config.api_key);
|
|
defer allocator.free(api_key_hash);
|
|
|
|
client.sendListJupyter(api_key_hash) catch |err| {
|
|
std.debug.print("Failed to send list command: {}\n", .{err});
|
|
return;
|
|
};
|
|
|
|
const response = client.receiveMessage(allocator) catch |err| {
|
|
std.debug.print("Failed to receive response: {}\n", .{err});
|
|
return;
|
|
};
|
|
defer allocator.free(response);
|
|
|
|
const packet = protocol.ResponsePacket.deserialize(response, allocator) catch |err| {
|
|
std.debug.print("Failed to parse response: {}\n", .{err});
|
|
return;
|
|
};
|
|
defer packet.deinit(allocator);
|
|
|
|
switch (packet.packet_type) {
|
|
.data => {
|
|
std.debug.print("Jupyter Services:\n", .{});
|
|
if (packet.data_payload) |payload| {
|
|
const parsed = std.json.parseFromSlice(std.json.Value, allocator, payload, .{}) catch {
|
|
std.debug.print("{s}\n", .{payload});
|
|
return;
|
|
};
|
|
defer parsed.deinit();
|
|
|
|
var services_opt: ?std.json.Array = null;
|
|
if (parsed.value == .array) {
|
|
services_opt = parsed.value.array;
|
|
} else if (parsed.value == .object) {
|
|
if (parsed.value.object.get("services")) |sv| {
|
|
if (sv == .array) services_opt = sv.array;
|
|
}
|
|
}
|
|
|
|
if (services_opt == null) {
|
|
std.debug.print("{s}\n", .{payload});
|
|
return;
|
|
}
|
|
|
|
const services = services_opt.?;
|
|
if (services.items.len == 0) {
|
|
std.debug.print("No running services.\n", .{});
|
|
return;
|
|
}
|
|
|
|
std.debug.print("NAME\t\t\t\t\t\t\t\t\t\t\tSTATUS\t\tURL\t\t\t\t\t\t\t\t\t\t\t\t\tWORKSPACE\n", .{});
|
|
std.debug.print("---- ------ --- ---------\n", .{});
|
|
|
|
for (services.items) |item| {
|
|
if (item != .object) continue;
|
|
const obj = item.object;
|
|
|
|
var name: []const u8 = "";
|
|
if (obj.get("name")) |v| {
|
|
if (v == .string) name = v.string;
|
|
}
|
|
var status: []const u8 = "";
|
|
if (obj.get("status")) |v| {
|
|
if (v == .string) status = v.string;
|
|
}
|
|
var url_str: []const u8 = "";
|
|
if (obj.get("url")) |v| {
|
|
if (v == .string) url_str = v.string;
|
|
}
|
|
var workspace: []const u8 = "";
|
|
if (obj.get("workspace")) |v| {
|
|
if (v == .string) workspace = v.string;
|
|
}
|
|
|
|
std.debug.print("{s: <20} {s: <9} {s: <25} {s}\n", .{ name, status, url_str, workspace });
|
|
}
|
|
}
|
|
},
|
|
.error_packet => {
|
|
const error_msg = protocol.ResponsePacket.getErrorMessage(packet.error_code.?);
|
|
std.debug.print("Failed to list services: {s}\n", .{error_msg});
|
|
if (packet.error_details) |details| {
|
|
std.debug.print("Details: {s}\n", .{details});
|
|
} else if (packet.error_message) |msg| {
|
|
std.debug.print("Details: {s}\n", .{msg});
|
|
}
|
|
},
|
|
else => {
|
|
std.debug.print("Unexpected response type\n", .{});
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Package management commands
|
|
pub fn packageCommands(args: []const []const u8) !void {
|
|
if (args.len < 1) {
|
|
std.debug.print("Usage: ml jupyter package <list>\n", .{});
|
|
return;
|
|
}
|
|
|
|
const subcommand = args[0];
|
|
|
|
if (std.mem.eql(u8, subcommand, "list")) {
|
|
if (args.len < 2) {
|
|
std.debug.print("Usage: ml jupyter package list <service-name>\n", .{});
|
|
return;
|
|
}
|
|
|
|
var service_name: []const u8 = "";
|
|
if (std.mem.eql(u8, args[1], "--name") and args.len >= 3) {
|
|
service_name = args[2];
|
|
} else {
|
|
service_name = args[1];
|
|
}
|
|
if (service_name.len == 0) {
|
|
std.debug.print("Service name is required\n", .{});
|
|
return;
|
|
}
|
|
|
|
const allocator = std.heap.page_allocator;
|
|
const config = try Config.load(allocator);
|
|
defer {
|
|
var mut_config = config;
|
|
mut_config.deinit(allocator);
|
|
}
|
|
|
|
const url = try config.getWebSocketUrl(allocator);
|
|
defer allocator.free(url);
|
|
|
|
var client = ws.Client.connect(allocator, url, config.api_key) catch |err| {
|
|
std.debug.print("Failed to connect to server: {}\n", .{err});
|
|
return;
|
|
};
|
|
defer client.close();
|
|
|
|
const api_key_hash = try crypto.hashApiKey(allocator, config.api_key);
|
|
defer allocator.free(api_key_hash);
|
|
|
|
client.sendListJupyterPackages(service_name, api_key_hash) catch |err| {
|
|
std.debug.print("Failed to send list packages command: {}\n", .{err});
|
|
return;
|
|
};
|
|
|
|
const response = client.receiveMessage(allocator) catch |err| {
|
|
std.debug.print("Failed to receive response: {}\n", .{err});
|
|
return;
|
|
};
|
|
defer allocator.free(response);
|
|
|
|
const packet = protocol.ResponsePacket.deserialize(response, allocator) catch |err| {
|
|
std.debug.print("Failed to parse response: {}\n", .{err});
|
|
return;
|
|
};
|
|
defer packet.deinit(allocator);
|
|
|
|
switch (packet.packet_type) {
|
|
.data => {
|
|
std.debug.print("Installed packages for {s}:\n", .{service_name});
|
|
if (packet.data_payload) |payload| {
|
|
const parsed = std.json.parseFromSlice(std.json.Value, allocator, payload, .{}) catch {
|
|
std.debug.print("{s}\n", .{payload});
|
|
return;
|
|
};
|
|
defer parsed.deinit();
|
|
|
|
if (parsed.value != .array) {
|
|
std.debug.print("{s}\n", .{payload});
|
|
return;
|
|
}
|
|
|
|
const pkgs = parsed.value.array;
|
|
if (pkgs.items.len == 0) {
|
|
std.debug.print("No packages found.\n", .{});
|
|
return;
|
|
}
|
|
|
|
std.debug.print("NAME VERSION SOURCE\n", .{});
|
|
std.debug.print("---- ------- ------\n", .{});
|
|
|
|
for (pkgs.items) |item| {
|
|
if (item != .object) continue;
|
|
const obj = item.object;
|
|
|
|
var name: []const u8 = "";
|
|
if (obj.get("name")) |v| {
|
|
if (v == .string) name = v.string;
|
|
}
|
|
var version: []const u8 = "";
|
|
if (obj.get("version")) |v| {
|
|
if (v == .string) version = v.string;
|
|
}
|
|
var source: []const u8 = "";
|
|
if (obj.get("source")) |v| {
|
|
if (v == .string) source = v.string;
|
|
}
|
|
|
|
std.debug.print("{s: <30} {s: <22} {s}\n", .{ name, version, source });
|
|
}
|
|
}
|
|
},
|
|
.error_packet => {
|
|
const error_msg = protocol.ResponsePacket.getErrorMessage(packet.error_code.?);
|
|
std.debug.print("Failed to list packages: {s}\n", .{error_msg});
|
|
if (packet.error_details) |details| {
|
|
std.debug.print("Details: {s}\n", .{details});
|
|
} else if (packet.error_message) |msg| {
|
|
std.debug.print("Details: {s}\n", .{msg});
|
|
}
|
|
},
|
|
else => {
|
|
std.debug.print("Unexpected response type\n", .{});
|
|
},
|
|
}
|
|
} else {
|
|
std.debug.print("Invalid package command: {s}\n", .{subcommand});
|
|
}
|
|
}
|