const std = @import("std"); const Config = @import("../config.zig").Config; const io = @import("../utils/io.zig"); const json = @import("../utils/json.zig"); const manifest = @import("../utils/manifest.zig"); const core = @import("../core.zig"); pub const Options = struct { json: bool = false, base: ?[]const u8 = null, }; pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { var flags = core.flags.CommonFlags{}; var base: ?[]const u8 = null; var target_path: ?[]const u8 = null; var i: usize = 0; while (i < args.len) : (i += 1) { const arg = args[i]; if (std.mem.eql(u8, arg, "--json")) { flags.json = true; } else if (std.mem.eql(u8, arg, "--base") and i + 1 < args.len) { base = args[i + 1]; i += 1; } else if (std.mem.startsWith(u8, arg, "--help")) { return printUsage(); } else if (std.mem.startsWith(u8, arg, "--")) { core.output.err("Unknown option"); return error.InvalidArgs; } else { target_path = arg; } } core.output.setMode(if (flags.json) .json else .text); if (target_path == null) { core.output.err("No target path specified"); return printUsage(); } const manifest_path = manifest.resolvePathWithBase(allocator, target_path.?, base) catch |err| { if (err == error.FileNotFound) { core.output.err("Manifest not found"); } return err; }; const data = try manifest.readFileAlloc(allocator, manifest_path); defer { allocator.free(manifest_path); allocator.free(data); } if (flags.json) { var out = io.stdoutWriter(); try out.print("{s}\n", .{data}); return; } const parsed = try std.json.parseFromSlice(std.json.Value, allocator, data, .{}); defer parsed.deinit(); if (parsed.value != .object) { core.output.err("run manifest is not a JSON object"); return error.InvalidManifest; } const root = parsed.value.object; const run_id = json.getString(root, "run_id") orelse ""; const task_id = json.getString(root, "task_id") orelse ""; const job_name = json.getString(root, "job_name") orelse ""; const commit_id = json.getString(root, "commit_id") orelse ""; const worker_version = json.getString(root, "worker_version") orelse ""; const podman_image = json.getString(root, "podman_image") orelse ""; const snapshot_id = json.getString(root, "snapshot_id") orelse ""; const snapshot_sha = json.getString(root, "snapshot_sha256") orelse ""; const command = json.getString(root, "command") orelse ""; const cmd_args = json.getString(root, "args") orelse ""; const exit_code = json.getInt(root, "exit_code"); const err_msg = json.getString(root, "error") orelse ""; const created_at = json.getString(root, "created_at") orelse ""; const started_at = json.getString(root, "started_at") orelse ""; const ended_at = json.getString(root, "ended_at") orelse ""; const staging_ms = json.getInt(root, "staging_duration_ms") orelse 0; const exec_ms = json.getInt(root, "execution_duration_ms") orelse 0; const finalize_ms = json.getInt(root, "finalize_duration_ms") orelse 0; const total_ms = json.getInt(root, "total_duration_ms") orelse 0; std.debug.print("run_manifest\t{s}\n", .{manifest_path}); if (job_name.len > 0) std.debug.print("job_name\t{s}\n", .{job_name}); if (run_id.len > 0) std.debug.print("run_id\t{s}\n", .{run_id}); if (task_id.len > 0) std.debug.print("task_id\t{s}\n", .{task_id}); if (commit_id.len > 0) std.debug.print("commit_id\t{s}\n", .{commit_id}); if (worker_version.len > 0) std.debug.print("worker_version\t{s}\n", .{worker_version}); if (podman_image.len > 0) std.debug.print("podman_image\t{s}\n", .{podman_image}); if (snapshot_id.len > 0) std.debug.print("snapshot_id\t{s}\n", .{snapshot_id}); if (snapshot_sha.len > 0) std.debug.print("snapshot_sha256\t{s}\n", .{snapshot_sha}); if (command.len > 0) { if (cmd_args.len > 0) { std.debug.print("command\t{s} {s}\n", .{ command, cmd_args }); } else { std.debug.print("command\t{s}\n", .{command}); } } if (created_at.len > 0) std.debug.print("created_at\t{s}\n", .{created_at}); if (started_at.len > 0) std.debug.print("started_at\t{s}\n", .{started_at}); if (ended_at.len > 0) std.debug.print("ended_at\t{s}\n", .{ended_at}); if (total_ms > 0 or staging_ms > 0 or exec_ms > 0 or finalize_ms > 0) { std.debug.print("durations_ms\ttotal={d}\tstaging={d}\texecution={d}\tfinalize={d}\n", .{ total_ms, staging_ms, exec_ms, finalize_ms }); } if (exit_code) |ec| { if (ec == 0 and err_msg.len == 0) { std.debug.print("exit_code\t0\n", .{}); } else { std.debug.print("exit_code\t{d}\n", .{ec}); } } if (err_msg.len > 0) { std.debug.print("error\t{s}\n", .{err_msg}); } } fn printUsage() !void { std.debug.print("Usage:\n", .{}); std.debug.print("\tml info [--json] [--base ]\n", .{}); } test "resolveManifestPath uses run_manifest.json for directories" { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); try tmp.dir.makeDir("run"); const run_abs = try tmp.dir.realpathAlloc(allocator, "run"); defer allocator.free(run_abs); const got = try manifest.resolvePathWithBase(allocator, run_abs, null); try std.testing.expect(std.mem.endsWith(u8, got, "run/run_manifest.json")); } test "resolveManifestPath resolves by task id when base is provided" { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); try tmp.dir.makePath("finished/run-a"); var file = try tmp.dir.createFile("finished/run-a/run_manifest.json", .{}); defer file.close(); try file.writeAll( "{\n" ++ " \"run_id\": \"run-a\",\n" ++ " \"task_id\": \"task-123\",\n" ++ " \"job_name\": \"job\"\n" ++ "}\n", ); const base_abs = try tmp.dir.realpathAlloc(allocator, "."); defer allocator.free(base_abs); const got = try manifest.resolvePathWithBase(allocator, "task-123", base_abs); try std.testing.expect(std.mem.endsWith(u8, got, "finished/run-a/run_manifest.json")); }