Comparison

plugins/mod_admin_shell.lua @ 13350:0573401b0f9f

mod_admin_shell: Support for 'shell-command' items (global and per-host) This should simplify adding shell commands from other modules, which will reduce the growth of mod_admin_shell and make it easier for community modules to expose commands too.
author Matthew Wild <mwild1@gmail.com>
date Wed, 29 Nov 2023 17:19:53 +0000
parent 13349:d5d4e386c6fb
child 13351:e8429d2faea4
comparison
equal deleted inserted replaced
13349:d5d4e386c6fb 13350:0573401b0f9f
15 local s2smanager = require "prosody.core.s2smanager"; 15 local s2smanager = require "prosody.core.s2smanager";
16 local portmanager = require "prosody.core.portmanager"; 16 local portmanager = require "prosody.core.portmanager";
17 local helpers = require "prosody.util.helpers"; 17 local helpers = require "prosody.util.helpers";
18 local it = require "prosody.util.iterators"; 18 local it = require "prosody.util.iterators";
19 local server = require "prosody.net.server"; 19 local server = require "prosody.net.server";
20 local schema = require "prosody.util.jsonschema";
20 local st = require "prosody.util.stanza"; 21 local st = require "prosody.util.stanza";
21 22
22 local _G = _G; 23 local _G = _G;
23 24
24 local prosody = _G.prosody; 25 local prosody = _G.prosody;
2420 end 2421 end
2421 end 2422 end
2422 return displayed_stats; 2423 return displayed_stats;
2423 end 2424 end
2424 2425
2426 local command_metadata_schema = {
2427 type = "object";
2428 properties = {
2429 section = { type = "string" };
2430 section_desc = { type = "string" };
2431
2432 name = { type = "string" };
2433 desc = { type = "string" };
2434 help = { type = "string" };
2435 args = {
2436 type = "array";
2437 items = {
2438 type = "object";
2439 properties = {
2440 name = { type = "string", required = true };
2441 type = { type = "string", required = false };
2442 };
2443 };
2444 };
2445 };
2446
2447 required = { "name", "section", "desc", "args" };
2448 };
2449
2450 -- host_commands[section..":"..name][host] = handler
2451 -- host_commands[section..":"..name][false] = metadata
2452 local host_commands = {};
2453
2454 local function new_item_handlers(command_host)
2455 local function on_command_added(event)
2456 local command = event.item;
2457 local mod_name = command._provided_by and ("mod_"..command._provided_by) or "<unknown module>";
2458 module:log("warn", "**************************************")
2459 if not schema.validate(command_metadata_schema, command) or type(command.handler) ~= "function" then
2460 module:log("warn", "Ignoring command added by %s: missing or invalid data", mod_name);
2461 return;
2462 end
2463
2464 local handler = command.handler;
2465
2466 if command_host then
2467 if type(command.host_selector) ~= "string" then
2468 module:log("warn", "Ignoring command %s:%s() added by %s - missing/invalid host_selector", command.section, command.name, mod_name);
2469 return;
2470 end
2471 local qualified_name = command.section..":"..command.name;
2472 local host_command_info = host_commands[qualified_name];
2473 if not host_command_info then
2474 local selector_index;
2475 for i, arg in ipairs(command.args) do
2476 if arg.name == command.host_selector then
2477 selector_index = i + 1; -- +1 to account for 'self'
2478 break;
2479 end
2480 end
2481 if not selector_index then
2482 module:log("warn", "Command %s() host selector argument '%s' not found - not registering", qualified_name, command.host_selector);
2483 return;
2484 end
2485 host_command_info = {
2486 [false] = {
2487 host_selector = command.host_selector;
2488 handler = function (...)
2489 local selected_host = select(2, jid_split((select(selector_index, ...))));
2490 if type(selected_host) ~= "string" then
2491 return nil, "Invalid or missing argument '"..command.host_selector.."'";
2492 end
2493 if not hosts[selected_host] then
2494 return nil, "Unknown host: "..selected_host;
2495 end
2496 local handler = host_commands[qualified_name][selected_host];
2497 if not handler then
2498 return nil, "This command is not available on "..selected_host;
2499 end
2500 return handler(...);
2501 end;
2502 };
2503 };
2504 host_commands[qualified_name] = host_command_info;
2505 end
2506 if host_command_info[command_host] then
2507 module:log("warn", "Command %s() is already registered - overwriting with %s", qualified_name, mod_name);
2508 end
2509 host_command_info[command_host] = handler;
2510 end
2511
2512 local section_t = def_env[command.section];
2513 if not section_t then
2514 section_t = {};
2515 def_env[command.section] = section_t;
2516 end
2517
2518 if command_host then
2519 section_t[command.name] = host_commands[command.section..":"..command.name][false].handler;
2520 else
2521 section_t[command.name] = command.handler;
2522 end
2523
2524 local section_mt = getmetatable(section_t);
2525 if not section_mt then
2526 section_mt = {};
2527 setmetatable(section_t, section_mt);
2528 end
2529 local section_help = section_mt.help;
2530 if not section_help then
2531 section_help = {
2532 desc = command.section_desc;
2533 commands = {};
2534 };
2535 section_mt.help = section_help;
2536 end
2537
2538 section_help.commands[command.name] = {
2539 desc = command.desc;
2540 full = command.help;
2541 args = array(command.args);
2542 module = command._provided_by;
2543 };
2544
2545 module:log("debug", "Shell command added by mod_%s: %s:%s()", mod_name, command.section, command.name);
2546 end
2547
2548 local function on_command_removed(event)
2549 local command = event.item;
2550
2551 local handler = event.item.handler;
2552 if type(handler) ~= "function" or not schema.validate(command_metadata_schema, command) then
2553 return;
2554 end
2555
2556 local section_t = def_env[command.section];
2557 if not section_t or section_t[command.name] ~= handler then
2558 return;
2559 end
2560
2561 section_t[command.name] = nil;
2562 if next(section_t) == nil then -- Delete section if empty
2563 def_env[command.section] = nil;
2564 end
2565
2566 if command_host then
2567 local host_command_info = host_commands[command.section..":"..command.name];
2568 if host_command_info then
2569 -- Remove our host handler
2570 host_command_info[command_host] = nil;
2571 -- Clean up entire command entry if there are no per-host handlers left
2572 local any_hosts = false;
2573 for k in pairs(host_command_info) do
2574 if k then -- metadata is false, ignore it
2575 any_hosts = true;
2576 break;
2577 end
2578 end
2579 if not any_hosts then
2580 host_commands[command.section..":"..command.name] = nil;
2581 end
2582 end
2583 end
2584 end
2585 return on_command_added, on_command_removed;
2586 end
2587
2588 module:handle_items("shell-command", new_item_handlers());
2589
2590 function module.add_host(host_module)
2591 host_module:log("warn", "Loaded on %s", host_module.host);
2592 host_module:handle_items("shell-command", new_item_handlers(host_module.host));
2593 end
2425 2594
2426 function module.unload() 2595 function module.unload()
2427 stanza_watchers.cleanup(); 2596 stanza_watchers.cleanup();
2428 end 2597 end
2429 2598