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 |