Software / code / prosody
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 |