Skip to content
Snippets Groups Projects
Unverified Commit 7ccdacd2 authored by Matthias Schiffer's avatar Matthias Schiffer
Browse files

treewide: rework check_site_lib.lua

In addition to significant internal differences in check_site_lib.lua (in
particular unifying error handling to a single place for the upcoming
multi-domain support), this changes the way fields are addressed in site
check scripts: rather than providing a string like 'next_node.ip6', the
path is passed as an array {'next_node', 'ip6'}.

Other changes in site check scripts:
* need_array and need_table now pass the full path to the sub fields to the
subcheck instead of the key and value
* Any check referring to a field inside a table implies that all higher
levels must be tables if they exist: a check for {'next_node', 'ip6'} adds
an implicit (optional) check for {'next_node'}, which allows to remove many
explicit checks for such tables
parent 414dfa81
No related branches found
No related tags found
No related merge requests found
Showing
with 199 additions and 232 deletions
need_string_array(in_site('authorized_keys'))
need_string_array(in_site({'authorized_keys'}))
need_string(in_site('autoupdater.branch'))
need_string(in_site({'autoupdater', 'branch'}))
local function check_branch(k, _)
assert_uci_name(k)
need_table({'autoupdater', 'branches'}, function(branch)
need_alphanumeric_key(branch)
local prefix = string.format('autoupdater.branches[%q].', k)
need_string(in_site(prefix .. 'name'))
need_string_array_match(prefix .. 'mirrors', '^http://')
need_number(in_site(prefix .. 'good_signatures'))
need_string_array_match(in_site(prefix .. 'pubkeys'), '^%x+$')
end
need_table('autoupdater.branches', check_branch)
need_string(in_site(extend(branch, {'name'})))
need_string_array_match(extend(branch, {'mirrors'}), '^http://')
need_number(in_site(extend(branch, {'good_signatures'})))
need_string_array_match(in_site(extend(branch, {'pubkeys'})), '^%x+$')
end)
need_string_match(in_domain('next_node.mac'), '^%x[02468aAcCeE]:%x%x:%x%x:%x%x:%x%x:%x%x$', false)
need_string_match(in_domain({'next_node', 'mac'}), '^%x[02468aAcCeE]:%x%x:%x%x:%x%x:%x%x:%x%x$', false)
if need_string_match(in_domain('next_node.ip4'), '^%d+.%d+.%d+.%d+$', false) then
need_string_match(in_domain('prefix4'), '^%d+.%d+.%d+.%d+/%d+$')
if need_string_match(in_domain({'next_node', 'ip4'}), '^%d+.%d+.%d+.%d+$', false) then
need_string_match(in_domain({'prefix4'}), '^%d+.%d+.%d+.%d+/%d+$')
end
need_string_match(in_domain('next_node.ip6'), '^[%x:]+$', false)
need_string_match(in_domain({'next_node', 'ip6'}), '^[%x:]+$', false)
for _, config in ipairs({'wifi24', 'wifi5'}) do
if need_table(config .. '.ap', nil, false) then
need_string(in_domain(config .. '.ap.ssid'))
need_boolean(config .. '.ap.disabled', false)
if need_table({config}, nil, false) then
need_string(in_domain({config, 'ap', 'ssid'}))
need_boolean({config, 'ap', 'disabled'}, false)
end
end
if need_table(in_site('config_mode'), nil, false) and need_table(in_site('config_mode.owner'), nil, false) then
need_boolean(in_site('config_mode.owner.obligatory'), false)
end
need_boolean(in_site({'config_mode', 'owner', 'obligatory'}), false)
if need_table(in_site('config_mode'), nil, false) and need_table(in_site('config_mode.geo_location'), nil, false) then
need_boolean(in_site('config_mode.geo_location.show_altitude'), false)
end
need_boolean(in_site({'config_mode', 'geo_location', 'show_altitude'}), false)
need_string(in_site('site_code'))
need_string(in_site('site_name'))
need_string_match(in_domain('domain_seed'), '^' .. ('%x'):rep(64) .. '$')
need_string(in_site({'site_code'}))
need_string(in_site({'site_name'}))
need_string_match(in_domain({'domain_seed'}), '^' .. ('%x'):rep(64) .. '$')
if need_table('opkg', nil, false) then
need_string('opkg.lede', false)
need_string({'opkg', 'lede'}, false)
need_table({'opkg', 'extra'}, function(extra_repo)
need_alphanumeric_key(extra_repo)
need_string(extra_repo)
end, false)
function check_repo(k, _)
-- this is not actually a uci name, but using the same naming rules here is fine
assert_uci_name(k)
need_string(in_site({'hostname_prefix'}), false)
need_string(in_site({'timezone'}))
local path = string.format('opkg.extra[%q]', k)
need_string(path)
end
need_table('opkg.extra', check_repo, false)
end
need_string(in_site('hostname_prefix'), false)
need_string(in_site('timezone'))
need_string_array({'ntp_servers'}, false)
need_string_array('ntp_servers', false)
need_string_match(in_domain('prefix6'), '^[%x:]+/64$')
need_string_match(in_domain({'prefix6'}), '^[%x:]+/64$')
for _, config in ipairs({'wifi24', 'wifi5'}) do
if need_table(config, nil, false) then
need_string(in_site('regdom')) -- regdom is only required when wifi24 or wifi5 is configured
if need_table({config}, nil, false) then
need_string(in_site({'regdom'})) -- regdom is only required when wifi24 or wifi5 is configured
need_number(config .. '.channel')
need_number({config, 'channel'})
local rates = {1000, 2000, 5500, 6000, 9000, 11000, 12000, 18000, 24000, 36000, 48000, 54000}
local supported_rates = need_array_of(in_site(config .. '.supported_rates'), rates, false)
if supported_rates then
need_array_of(config .. '.basic_rate', supported_rates, true)
else
need_array_of(config .. '.basic_rate', rates, false)
end
end
end
local supported_rates = need_array_of(in_site({config, 'supported_rates'}), rates, false)
need_array_of({config, 'basic_rate'}, supported_rates or rates, supported_rates ~= nil)
need_boolean(in_site('poe_passthrough'), false)
if need_table('dns', nil, false) then
need_number('dns.cacheentries', false)
need_string_array_match('dns.servers', '^[%x:]+$', true)
if need_table({config, 'ibss'}, nil, false) then
need_string(in_domain({config, 'ibss', 'ssid'}))
need_string_match(in_domain({config, 'ibss', 'bssid'}), '^%x[02468aAcCeE]:%x%x:%x%x:%x%x:%x%x:%x%x$')
need_one_of({config, 'ibss', 'mcast_rate'}, supported_rates or rates, false)
need_number({config, 'ibss', 'vlan'}, false)
need_boolean({config, 'ibss', 'disabled'}, false)
end
if need_table('next_node', nil, false) then
need_string_match(in_domain('next_node.ip6'), '^[%x:]+$', false)
need_string_match(in_domain('next_node.ip4'), '^%d+.%d+.%d+.%d+$', false)
if need_table({config, 'mesh'}, nil, false) then
need_string(in_domain({config, 'mesh', 'id'}))
need_one_of({config, 'mesh', 'mcast_rate'}, supported_rates or rates, false)
need_boolean({config, 'mesh', 'disabled'}, false)
end
end
end
for _, config in ipairs({'wifi24', 'wifi5'}) do
local rates = {1000, 2000, 5500, 6000, 9000, 11000, 12000, 18000, 24000, 36000, 48000, 54000}
rates = need_array_of(in_site(config .. '.supported_rates'), rates, false) or rates
need_boolean(in_site({'poe_passthrough'}), false)
if need_table(config .. '.ibss', nil, false) then
need_string(in_domain(config .. '.ibss.ssid'))
need_string_match(in_domain(config .. '.ibss.bssid'), '^%x[02468aAcCeE]:%x%x:%x%x:%x%x:%x%x:%x%x$')
need_one_of(config .. '.ibss.mcast_rate', rates, false)
need_number(config .. '.ibss.vlan', false)
need_boolean(config .. '.ibss.disabled', false)
if need_table({'dns'}, nil, false) then
need_string_array_match({'dns', 'servers'}, '^[%x:]+$')
need_number({'dns', 'cacheentries'}, false)
end
if need_table(config .. '.mesh', nil, false) then
need_string(in_domain(config .. '.mesh.id'))
need_one_of(config .. '.mesh.mcast_rate', rates, false)
need_boolean(config .. '.mesh.disabled', false)
end
end
need_string_match(in_domain({'next_node', 'ip6'}), '^[%x:]+$', false)
need_string_match(in_domain({'next_node', 'ip4'}), '^%d+.%d+.%d+.%d+$', false)
need_boolean(in_site('mesh_on_wan'), false)
need_boolean(in_site('mesh_on_lan'), false)
need_boolean(in_site('single_as_lan'), false)
need_boolean(in_site({'mesh_on_wan'}), false)
need_boolean(in_site({'mesh_on_lan'}), false)
need_boolean(in_site({'single_as_lan'}), false)
need_string_match(in_domain('prefix4'), '^%d+.%d+.%d+.%d+/%d+$', false)
need_string_array_match(in_domain('extra_prefixes6'), '^[%x:]+/%d+$', false)
need_string_match(in_domain({'prefix4'}), '^%d+.%d+.%d+.%d+/%d+$', false)
need_string_array_match(in_domain({'extra_prefixes6'}), '^[%x:]+/%d+$', false)
if need_table('mesh', nil, false) and need_table('mesh.batman_adv', nil, false) then
need_number('mesh.batman_adv.gw_sel_class', false)
need_one_of('mesh.batman_adv.routing_algo', {'BATMAN_IV', 'BATMAN_V'}, false)
end
need_number({'mesh', 'batman_adv', 'gw_sel_class'}, false)
need_one_of({'mesh', 'batman_adv', 'routing_algo'}, {'BATMAN_IV', 'BATMAN_V'}, false)
need_boolean(in_site('mesh_vpn.enabled'), false)
need_number('mesh_vpn.mtu')
need_boolean(in_site({'mesh_vpn', 'enabled'}), false)
need_number({'mesh_vpn', 'mtu'})
if need_table(in_site('mesh_vpn.bandwidth_limit'), nil, false) then
need_boolean(in_site('mesh_vpn.bandwidth_limit.enabled'), false)
need_number(in_site('mesh_vpn.bandwidth_limit.ingress'), false)
need_number(in_site('mesh_vpn.bandwidth_limit.egress'), false)
end
need_boolean(in_site({'mesh_vpn', 'bandwidth_limit', 'enabled'}), false)
need_number(in_site({'mesh_vpn', 'bandwidth_limit', 'ingress'}), false)
need_number(in_site({'mesh_vpn', 'bandwidth_limit', 'egress'}), false)
local fastd_methods = {'salsa2012+gmac', 'salsa2012+umac', 'null+salsa2012+gmac', 'null+salsa2012+umac', 'null'}
need_array_of('mesh_vpn.fastd.methods', fastd_methods)
need_boolean(in_site('mesh_vpn.fastd.configurable'), false)
need_array_of({'mesh_vpn', 'fastd', 'methods'}, fastd_methods)
need_boolean(in_site({'mesh_vpn', 'fastd', 'configurable'}), false)
need_one_of(in_site('mesh_vpn.fastd.syslog_level'), {'error', 'warn', 'info', 'verbose', 'debug', 'debug2'}, false)
need_one_of(in_site({'mesh_vpn', 'fastd', 'syslog_level'}), {'error', 'warn', 'info', 'verbose', 'debug', 'debug2'}, false)
local function check_peer(prefix)
return function(k, _)
assert_uci_name(k)
local function check_peer(k)
need_alphanumeric_key(k)
local table = string.format('%s[%q].', prefix, k)
need_string_match(in_domain(table .. 'key'), '^%x+$')
need_string_array(in_domain(table .. 'remotes'))
end
need_string_match(in_domain(extend(k, {'key'})), '^%x+$')
need_string_array(in_domain(extend(k, {'remotes'})))
end
local function check_group(prefix)
return function(k, _)
assert_uci_name(k)
local function check_group(k)
need_alphanumeric_key(k)
local table = string.format('%s[%q].', prefix, k)
need_number(table .. 'limit', false)
need_table(table .. 'peers', check_peer(table .. 'peers'), false)
need_table(table .. 'groups', check_group(table .. 'groups'), false)
end
need_number(extend(k, {'limit'}), false)
need_table(extend(k, {'peers'}), check_peer, false)
need_table(extend(k, {'groups'}), check_group, false)
end
need_table('mesh_vpn.fastd.groups', check_group('mesh_vpn.fastd.groups'))
need_table({'mesh_vpn', 'fastd', 'groups'}, check_group)
need_string_array('mesh_vpn.tunneldigger.brokers')
need_string_array({'mesh_vpn', 'tunneldigger', 'brokers'})
need_string(in_site('roles.default'), false)
need_string(in_site({'roles', 'default'}), false)
need_boolean(in_site('setup_mode.skip'), false)
need_boolean(in_site({'setup_mode', 'skip'}), false)
if need_table(in_site('config_mode'), nil, false) and need_table(in_site('config_mode.remote_login'), nil, false) then
need_boolean(in_site('config_mode.remote_login.show_password_form'), false)
need_number(in_site('config_mode.remote_login.min_password_length'), false)
end
need_boolean(in_site({'config_mode', 'remote_login', 'show_password_form'}), false)
need_number(in_site({'config_mode', 'remote_login', 'min_password_length'}), false)
assert(need_boolean(in_site('mesh_vpn.fastd.configurable')) == true,
"site.conf error: expected `mesh_vpn.fastd.configurable' to be true")
need_value(in_site({'mesh_vpn', 'fastd', 'configurable'}), true)
need_string(in_site('roles.default'))
need_string_array(in_site('roles.list'))
need_string(in_site({'roles', 'default'}))
need_string_array(in_site({'roles', 'list'}))
......@@ -7,154 +7,162 @@ function in_domain(var)
end
local function loadvar(varname)
local ok, val = pcall(assert(loadstring('return site.' .. varname)))
if ok then
return val
else
return nil
end
local function path_to_string(path)
return table.concat(path, '/')
end
local function array_to_string(array)
local string = ''
for _, v in ipairs(array) do
if #string >= 1 then
string = string .. ', '
return '[' .. table.concat(array, ', ') .. ']'
end
string = string .. v
end
return '[' .. string .. ']'
local function var_error(path, val, msg)
print(string.format('*** site.conf error: expected %s to %s, but it is %s', path_to_string(path), msg, tostring(val)))
os.exit(1)
end
local function assert_one_of(var, array, msg)
for _, v in ipairs(array) do
if v == var then
return true
function extend(path, c)
local p = {unpack(path)}
for _, e in ipairs(c) do
p[#p+1] = e
end
return p
end
error(msg)
local function loadpath(path, base, c, ...)
if not c or base == nil then
return base
end
local function assert_type(var, t, msg)
assert(type(var) == t, msg)
if type(base) ~= 'table' then
var_error(path, base, 'be a table')
end
return loadpath(extend(path, {c}), base[c], ...)
end
function assert_uci_name(var)
-- We don't use character classes like %w here to be independent of the locale
assert(var:match('^[0-9a-zA-Z_]+$'), "site.conf error: `" .. var .. "' is not a valid config section name (only alphanumeric characters and the underscore are allowed)")
local function loadvar(path)
return loadpath({}, site, unpack(path))
end
local function check_type(t)
return function(val)
return type(val) == t
end
end
function need_string(varname, required)
local var = loadvar(varname)
local function check_one_of(array)
return function(val)
for _, v in ipairs(array) do
if v == val then
return true
end
end
return false
end
end
if required == false and var == nil then
function need(path, check, required, msg)
local val = loadvar(path)
if required == false and val == nil then
return nil
end
assert_type(var, 'string', "site.conf error: expected `" .. varname .. "' to be a string")
return var
if not check(val) then
var_error(path, val, msg)
end
function need_string_match(varname, pat, required)
local var = need_string(varname, required)
if not var then
return nil
return val
end
assert(var:match(pat), "site.conf error: expected `" .. varname .. "' to match pattern `" .. pat .. "'")
return var
local function need_type(path, type, required, msg)
return need(path, check_type(type), required, msg)
end
function need_number(varname, required)
local var = loadvar(varname)
if required == false and var == nil then
return nil
function need_alphanumeric_key(path)
local val = path[#path]
-- We don't use character classes like %w here to be independent of the locale
if not val:match('^[0-9a-zA-Z_]+$') then
var_error(path, val, 'have a key using only alphanumeric characters and underscores')
end
end
assert_type(var, 'number', "site.conf error: expected `" .. varname .. "' to be a number")
return var
function need_string(path, required)
return need_type(path, 'string', required, 'be a string')
end
function need_boolean(varname, required)
local var = loadvar(varname)
if required == false and var == nil then
function need_string_match(path, pat, required)
local val = need_string(path, required)
if not val then
return nil
end
assert_type(var, 'boolean', "site.conf error: expected `" .. varname .. "' to be a boolean")
return var
if not val:match(pat) then
var_error(path, val, "match pattern '" .. pat .. "'")
end
function need_array(varname, subcheck, required)
local var = loadvar(varname)
return val
end
if required == false and var == nil then
return nil
function need_number(path, required)
return need_type(path, 'number', required, 'be a number')
end
assert_type(var, 'table', "site.conf error: expected `" .. varname .. "' to be an array")
function need_boolean(path, required)
return need_type(path, 'boolean', required, 'be a boolean')
end
for _, e in ipairs(var) do
subcheck(e)
function need_array(path, subcheck, required)
local val = need_type(path, 'table', required, 'be an array')
if not val then
return nil
end
return var
if subcheck then
for i = 1, #val do
subcheck(extend(path, {i}))
end
end
function need_table(varname, subcheck, required)
local var = loadvar(varname)
return val
end
if required == false and var == nil then
function need_table(path, subcheck, required)
local val = need_type(path, 'table', required, 'be a table')
if not val then
return nil
end
assert_type(var, 'table', "site.conf error: expected `" .. varname .. "' to be a table")
if subcheck then
for k, v in pairs(var) do
subcheck(k, v)
for k, _ in pairs(val) do
subcheck(extend(path, {k}))
end
end
return var
return val
end
function need_one_of(varname, array, required)
local var = loadvar(varname)
if required == false and var == nil then
return nil
function need_value(path, value, required)
return need(path, function(v)
return v == value
end, required, 'be ' .. tostring(value))
end
assert_one_of(var, array, "site.conf error: expected `" .. varname .. "' to be one of given array: " .. array_to_string(array))
return var
function need_one_of(path, array, required)
return need(path, check_one_of(array), required, 'be one of the given array ' .. array_to_string(array))
end
function need_string_array(varname, required)
local ok, var = pcall(need_array, varname, function(e) assert_type(e, 'string') end, required)
assert(ok, "site.conf error: expected `" .. varname .. "' to be a string array")
return var
function need_string_array(path, required)
return need_array(path, need_string, required)
end
function need_string_array_match(varname, pat, required)
local ok, var = pcall(need_array, varname, function(e) assert(e:match(pat)) end, required)
assert(ok, "site.conf error: expected `" .. varname .. "' to be a string array matching pattern `" .. pat .. "'")
return var
function need_string_array_match(path, pat, required)
return need_array(path, function(e) need_string_match(e, pat) end, required)
end
function need_array_of(varname, array, required)
local ok, var = pcall(need_array, varname, function(e) assert_one_of(e, array) end,required)
assert(ok, "site.conf error: expected `" .. varname .. "' to be a subset of given array: " .. array_to_string(array))
return var
function need_array_of(path, array, required)
return need_array(path, function(e) need_one_of(e, array) end, required)
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment