luci/applications/luci-app-example/root/usr/libexec/rpcd/luci.example
Duncan Hill 28f805b2e5
luci-app-example: Update with more documentation, more examples (#6503)
* luci-app-example: Update with more documentation, examples
* Update translations file
* Move more YAML support to .md file, improve README
* luci-app-example: Update with more documentation, examples
* luci-app-example: Fix missed call to load_sample_yaml
* Format with tabs by using jsbeautify
2023-12-04 22:12:28 +01:00

248 lines
8.3 KiB
Lua
Executable file

#!/usr/bin/env lua
-- If you need filesystem access, use nixio.fs
local fs = require "nixio.fs"
-- LuCI JSON is used for checking the arguments and converting tables to JSON.
local jsonc = require "luci.jsonc"
-- Nixio provides syslog functionality
local nixio = require "nixio"
-- To access /etc/config files, use the uci module
local UCI = require "luci.model.uci"
-- Slight overkill, but leaving room to do log_info etcetera.
local function log_to_syslog(level, message) nixio.syslog(level, message) end
local function log_error(message)
log_to_syslog("err", "[luci.example]: " .. message)
end
local function using_uci_directly(section)
-- Rather than parse files in /etc/config, you can rely on the
-- luci.model.uci module.
local uci = UCI.cursor()
-- https://openwrt.github.io/luci/api/modules/luci.model.uci.html
local config_name = uci:get("example", section)
uci.unload("example")
if not config_name then
local msg = "'" .. section .. "' not found in /etc/config/example"
-- Send the log message to syslog so it can be found with logread
log_error(msg)
-- Convert a lua table into JSON notation and print to stdout
-- .stringify() is equivalent to cjson's .encode()
print(jsonc.stringify({uci_error = msg}))
-- Indicate failure in the return code
os.exit(1)
end
return config_name
end
-- The methods table defines all of the APIs to expose to rpcd.
-- rpcd will execute this Lua file with the 'list' argument to discover the
-- method names that can be presented over ubus, as well as any arguments
-- those methods take.
local methods = {
-- How to call this API:
-- echo '{"section": "first"}' | lua /usr/libexec/rpcd/luci.example call get_uci_value
-- echo '{"section": "does_not_exist"}' | lua /usr/libexec/rpcd/luci.example call get_uci_value
get_uci_value = {
-- Args are specified as a table, where the argument type is specified by example
-- The value is not used as a default.
args = {section = "a_string"},
-- A special key of 'call' points to a function definition for execution.
call = function(args)
-- A table for the result.
local r = {}
r.result = jsonc.stringify({
example_section = using_uci_directly(args.section)
})
-- The 'call' handler will refer to '.code', but also defaults if not found.
r.code = 0
-- Return the table object; the call handler will access the attributes
-- of the table.
return r
end
},
-- How to call this API:
-- echo '{}' | lua /usr/libexec/rpcd/luci.example call get_sample1
-- ubus call luci.example get_sample1
get_sample1 = {
call = function()
local r = {}
-- This structure does not map well to a JSONMap in the LuCI form setup.
-- It can be rendered as a table easily enough with loops.
r.result = jsonc.stringify({
num_cats = 1,
num_dogs = 2,
num_parakeets = 4,
is_this_real = false,
not_found = nil
})
return r
end
},
-- How to call this API:
-- echo '{}' | lua /usr/libexec/rpcd/luci.example call get_sample2
-- ubus call luci.example get_sample2
get_sample2 = {
call = function()
local r = {}
-- This is the structural data that JSONMap will work with in the JS file
local data = {
option_one = {
name = "Some string value",
value = "A value string",
parakeets = {"one", "two", "three"},
},
option_two = {
name = "Another string value",
value = "And another value",
parakeets = {3, 4, 5},
}
}
r.result = jsonc.stringify(data)
return r
end
}
}
local function parseInput()
-- Input parsing - the RPC daemon calls the Lua script and
-- sends input to it via stdin, not as an argument on the CLI.
-- Thus, any testing via the lua interpreter needs to be in the form
-- echo '{jsondata}' | lua /usr/libexec/rpcd/script call method_name
local parse = jsonc.new()
local done, err
while true do
local chunk = io.read(4096)
if not chunk then
break
elseif not done and not err then
done, err = parse:parse(chunk)
end
end
if not done then
print(jsonc.stringify({
error = err or "Incomplete input for argument parsing"
}))
os.exit(1)
end
return parse:get()
end
local function validateArgs(func, uargs)
-- Validates that arguments picked out by parseInput actually match
-- up to the arguments expected by the function being called.
local method = methods[func]
if not method then
print(jsonc.stringify({error = "Method not found in methods table"}))
os.exit(1)
end
-- Lua has no length operator for tables, so iterate to get the count
-- of the keys.
local n = 0
for _, _ in pairs(uargs) do n = n + 1 end
-- If the method defines an args table (so empty tables are not allowed),
-- and there were no args, then give a useful error message about that.
if method.args and n == 0 then
print(jsonc.stringify({
error = "Received empty arguments for " .. func ..
" but it requires " .. jsonc.stringify(method.args)
}))
os.exit(1)
end
uargs.ubus_rpc_session = nil
local margs = method.args or {}
for k, v in pairs(uargs) do
if margs[k] == nil or (v ~= nil and type(v) ~= type(margs[k])) then
print(jsonc.stringify({
error = "Invalid argument '" .. k .. "' for " .. func ..
" it requires " .. jsonc.stringify(method.args)
}))
os.exit(1)
end
end
return method
end
if arg[1] == "list" then
-- When rpcd starts up, it executes all scripts in /usr/libexec/rpcd
-- passing 'list' as the first argument. This block of code examines
-- all of the entries in the methods table, and looks for an attribute
-- called 'args' to see if there are arguments for the method.
--
-- The end result is a JSON struct like
-- {
-- "api_name": {},
-- "api2_name": {"host": "some_string"}
-- }
--
-- Which will be converted by ubus to
-- "api_name":{}
-- "api2_name":{"host":"String"}
local _, rv = nil, {}
for _, method in pairs(methods) do rv[_] = method.args or {} end
print((jsonc.stringify(rv):gsub(":%[%]", ":{}")))
elseif arg[1] == "call" then
-- rpcd will execute the Lua script with a first argument of 'call',
-- a second argument of the method name, and a third argument that's
-- stringified JSON.
--
-- To debug your script, it's probably easiest to start with direct
-- execution, as calling via ubus will hide execution errors. For example:
-- echo '{}' | lua /usr/libexec/rpcd/luci.example call get_sample2
--
-- or
--
-- echo '{"section": "firstf"}' | /usr/libexec/rpcd/luci.example call get_uci_value
--
-- See https://openwrt.org/docs/techref/ubus for more details on using
-- ubus to call your RPC script (which is what LuCI will be doing).
local args = parseInput()
local method = validateArgs(arg[2], args)
local run = method.call(args)
-- Use the result from the table which we know to be JSON already.
-- Anything printed on stdout is sent via rpcd to the caller. Use
-- the syslog functions, or logging to a file, if you need debug
-- logs.
print(run.result)
-- And exit with the code supplied.
os.exit(run.code or 0)
elseif arg[1] == "help" then
local helptext = [[
Usage:
To see what methods are exported by this script:
lua luci.example list
To call a method that has no arguments:
echo '{}' | lua luci.example call method_name
To call a method that takes arguments:
echo '{"valid": "json", "argument": "value"}' | lua luci.example call method_name
To call this script via ubus:
ubus call luci.example method_name '{"valid": "json", "argument": "value"}'
]]
print(helptext)
end