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, _) need_table({'autoupdater', 'branches'}, function(branch)
assert_uci_name(k) need_alphanumeric_key(branch)
local prefix = string.format('autoupdater.branches[%q].', k) need_string(in_site(extend(branch, {'name'})))
need_string_array_match(extend(branch, {'mirrors'}), '^http://')
need_string(in_site(prefix .. 'name')) need_number(in_site(extend(branch, {'good_signatures'})))
need_string_array_match(prefix .. 'mirrors', '^http://') need_string_array_match(in_site(extend(branch, {'pubkeys'})), '^%x+$')
need_number(in_site(prefix .. 'good_signatures')) end)
need_string_array_match(in_site(prefix .. 'pubkeys'), '^%x+$')
end
need_table('autoupdater.branches', check_branch)
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 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+$') need_string_match(in_domain({'prefix4'}), '^%d+.%d+.%d+.%d+/%d+$')
end 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 for _, config in ipairs({'wifi24', 'wifi5'}) do
if need_table(config .. '.ap', nil, false) then if need_table({config}, nil, false) then
need_string(in_domain(config .. '.ap.ssid')) need_string(in_domain({config, 'ap', 'ssid'}))
need_boolean(config .. '.ap.disabled', false) need_boolean({config, 'ap', 'disabled'}, false)
end end
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)
need_boolean(in_site('config_mode.owner.obligatory'), false)
end
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)
need_boolean(in_site('config_mode.geo_location.show_altitude'), false)
end
need_string(in_site('site_code')) need_string(in_site({'site_code'}))
need_string(in_site('site_name')) need_string(in_site({'site_name'}))
need_string_match(in_domain('domain_seed'), '^' .. ('%x'):rep(64) .. '$') 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, _) need_string(in_site({'hostname_prefix'}), false)
-- this is not actually a uci name, but using the same naming rules here is fine need_string(in_site({'timezone'}))
assert_uci_name(k)
local path = string.format('opkg.extra[%q]', k) need_string_array({'ntp_servers'}, false)
need_string(path)
end
need_table('opkg.extra', check_repo, false)
end
need_string(in_site('hostname_prefix'), false) need_string_match(in_domain({'prefix6'}), '^[%x:]+/64$')
need_string(in_site('timezone'))
need_string_array('ntp_servers', false)
need_string_match(in_domain('prefix6'), '^[%x:]+/64$')
for _, config in ipairs({'wifi24', 'wifi5'}) do for _, config in ipairs({'wifi24', 'wifi5'}) do
if need_table(config, nil, false) then if need_table({config}, nil, false) then
need_string(in_site('regdom')) -- regdom is only required when wifi24 or wifi5 is configured 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 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) 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 or rates, supported_rates ~= nil)
need_array_of(config .. '.basic_rate', supported_rates, true)
else if need_table({config, 'ibss'}, nil, false) then
need_array_of(config .. '.basic_rate', rates, false) 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({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 end
end end
need_boolean(in_site('poe_passthrough'), false) 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)
end
if need_table('next_node', nil, false) then if need_table({'dns'}, nil, false) then
need_string_match(in_domain('next_node.ip6'), '^[%x:]+$', false) need_string_array_match({'dns', 'servers'}, '^[%x:]+$')
need_string_match(in_domain('next_node.ip4'), '^%d+.%d+.%d+.%d+$', false) need_number({'dns', 'cacheentries'}, false)
end end
for _, config in ipairs({'wifi24', 'wifi5'}) do need_string_match(in_domain({'next_node', 'ip6'}), '^[%x:]+$', false)
local rates = {1000, 2000, 5500, 6000, 9000, 11000, 12000, 18000, 24000, 36000, 48000, 54000} need_string_match(in_domain({'next_node', 'ip4'}), '^%d+.%d+.%d+.%d+$', false)
rates = need_array_of(in_site(config .. '.supported_rates'), rates, false) or rates
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)
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_boolean(in_site('mesh_on_wan'), false) need_boolean(in_site({'mesh_on_wan'}), false)
need_boolean(in_site('mesh_on_lan'), false) need_boolean(in_site({'mesh_on_lan'}), false)
need_boolean(in_site('single_as_lan'), false) need_boolean(in_site({'single_as_lan'}), false)
need_string_match(in_domain('prefix4'), '^%d+.%d+.%d+.%d+/%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) 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_number('mesh.batman_adv.gw_sel_class', false) need_one_of({'mesh', 'batman_adv', 'routing_algo'}, {'BATMAN_IV', 'BATMAN_V'}, false)
need_one_of('mesh.batman_adv.routing_algo', {'BATMAN_IV', 'BATMAN_V'}, false)
end
need_boolean(in_site('mesh_vpn.enabled'), false) need_boolean(in_site({'mesh_vpn', 'enabled'}), false)
need_number('mesh_vpn.mtu') 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_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.ingress'), false) need_number(in_site({'mesh_vpn', 'bandwidth_limit', 'egress'}), false)
need_number(in_site('mesh_vpn.bandwidth_limit.egress'), false)
end
local fastd_methods = {'salsa2012+gmac', 'salsa2012+umac', 'null+salsa2012+gmac', 'null+salsa2012+umac', 'null'} local fastd_methods = {'salsa2012+gmac', 'salsa2012+umac', 'null+salsa2012+gmac', 'null+salsa2012+umac', 'null'}
need_array_of('mesh_vpn.fastd.methods', fastd_methods) need_array_of({'mesh_vpn', 'fastd', 'methods'}, fastd_methods)
need_boolean(in_site('mesh_vpn.fastd.configurable'), false) 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) local function check_peer(k)
return function(k, _) need_alphanumeric_key(k)
assert_uci_name(k)
local table = string.format('%s[%q].', prefix, k) need_string_match(in_domain(extend(k, {'key'})), '^%x+$')
need_string_array(in_domain(extend(k, {'remotes'})))
need_string_match(in_domain(table .. 'key'), '^%x+$')
need_string_array(in_domain(table .. 'remotes'))
end
end end
local function check_group(prefix) local function check_group(k)
return function(k, _) need_alphanumeric_key(k)
assert_uci_name(k)
local table = string.format('%s[%q].', prefix, k)
need_number(table .. 'limit', false) need_number(extend(k, {'limit'}), false)
need_table(table .. 'peers', check_peer(table .. 'peers'), false) need_table(extend(k, {'peers'}), check_peer, false)
need_table(table .. 'groups', check_group(table .. 'groups'), false) need_table(extend(k, {'groups'}), check_group, false)
end
end 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_boolean(in_site('config_mode.remote_login.show_password_form'), false) need_number(in_site({'config_mode', 'remote_login', 'min_password_length'}), false)
need_number(in_site('config_mode.remote_login.min_password_length'), false)
end
assert(need_boolean(in_site('mesh_vpn.fastd.configurable')) == true, need_value(in_site({'mesh_vpn', 'fastd', 'configurable'}), true)
"site.conf error: expected `mesh_vpn.fastd.configurable' to be true")
need_string(in_site('roles.default')) need_string(in_site({'roles', 'default'}))
need_string_array(in_site('roles.list')) need_string_array(in_site({'roles', 'list'}))
function in_site(var) function in_site(var)
return var return var
end end
function in_domain(var) function in_domain(var)
return var return var
end end
local function loadvar(varname) local function path_to_string(path)
local ok, val = pcall(assert(loadstring('return site.' .. varname))) return table.concat(path, '/')
if ok then
return val
else
return nil
end
end end
local function array_to_string(array) local function array_to_string(array)
local string = '' return '[' .. table.concat(array, ', ') .. ']'
for _, v in ipairs(array) do
if #string >= 1 then
string = string .. ', '
end
string = string .. v
end
return '[' .. string .. ']'
end end
local function assert_one_of(var, array, msg) local function var_error(path, val, msg)
for _, v in ipairs(array) do print(string.format('*** site.conf error: expected %s to %s, but it is %s', path_to_string(path), msg, tostring(val)))
if v == var then os.exit(1)
return true
end
end
error(msg)
end end
local function assert_type(var, t, msg)
assert(type(var) == t, msg)
end
function extend(path, c)
local p = {unpack(path)}
function assert_uci_name(var) for _, e in ipairs(c) do
-- We don't use character classes like %w here to be independent of the locale p[#p+1] = e
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)") end
return p
end end
local function loadpath(path, base, c, ...)
if not c or base == nil then
return base
end
function need_string(varname, required) if type(base) ~= 'table' then
local var = loadvar(varname) var_error(path, base, 'be a table')
end
if required == false and var == nil then return loadpath(extend(path, {c}), base[c], ...)
return nil
end
assert_type(var, 'string', "site.conf error: expected `" .. varname .. "' to be a string")
return var
end end
function need_string_match(varname, pat, required) local function loadvar(path)
local var = need_string(varname, required) return loadpath({}, site, unpack(path))
end
if not var then
return nil
end
assert(var:match(pat), "site.conf error: expected `" .. varname .. "' to match pattern `" .. pat .. "'")
return var local function check_type(t)
return function(val)
return type(val) == t
end
end end
function need_number(varname, required) local function check_one_of(array)
local var = loadvar(varname) 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)
return nil local val = loadvar(path)
end if required == false and val == nil then
return nil
end
assert_type(var, 'number', "site.conf error: expected `" .. varname .. "' to be a number") if not check(val) then
var_error(path, val, msg)
end
return var return val
end end
function need_boolean(varname, required) local function need_type(path, type, required, msg)
local var = loadvar(varname) return need(path, check_type(type), required, msg)
end
if required == false and var == nil then
return nil
end
assert_type(var, 'boolean', "site.conf error: expected `" .. varname .. "' to be a boolean")
return var 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 end
function need_array(varname, subcheck, required)
local var = loadvar(varname)
if required == false and var == nil then function need_string(path, required)
return nil return need_type(path, 'string', required, 'be a string')
end end
assert_type(var, 'table', "site.conf error: expected `" .. varname .. "' to be an array") function need_string_match(path, pat, required)
local val = need_string(path, required)
if not val then
return nil
end
for _, e in ipairs(var) do if not val:match(pat) then
subcheck(e) var_error(path, val, "match pattern '" .. pat .. "'")
end end
return var return val
end end
function need_table(varname, subcheck, required) function need_number(path, required)
local var = loadvar(varname) return need_type(path, 'number', required, 'be a number')
end
if required == false and var == nil then function need_boolean(path, required)
return nil return need_type(path, 'boolean', required, 'be a boolean')
end end
assert_type(var, 'table', "site.conf error: expected `" .. varname .. "' to be a table") function need_array(path, subcheck, required)
local val = need_type(path, 'table', required, 'be an array')
if not val then
return nil
end
if subcheck then if subcheck then
for k, v in pairs(var) do for i = 1, #val do
subcheck(k, v) subcheck(extend(path, {i}))
end end
end end
return var return val
end end
function need_one_of(varname, array, required) function need_table(path, subcheck, required)
local var = loadvar(varname) local val = need_type(path, 'table', required, 'be a table')
if not val then
return nil
end
if required == false and var == nil then if subcheck then
return nil for k, _ in pairs(val) do
end subcheck(extend(path, {k}))
end
end
assert_one_of(var, array, "site.conf error: expected `" .. varname .. "' to be one of given array: " .. array_to_string(array)) return val
end
function need_value(path, value, required)
return need(path, function(v)
return v == value
end, required, 'be ' .. tostring(value))
end
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 end
function need_string_array(varname, required) function need_string_array(path, required)
local ok, var = pcall(need_array, varname, function(e) assert_type(e, 'string') end, required) return need_array(path, need_string, required)
assert(ok, "site.conf error: expected `" .. varname .. "' to be a string array")
return var
end end
function need_string_array_match(varname, pat, required) function need_string_array_match(path, pat, required)
local ok, var = pcall(need_array, varname, function(e) assert(e:match(pat)) end, required) return need_array(path, function(e) need_string_match(e, pat) end, required)
assert(ok, "site.conf error: expected `" .. varname .. "' to be a string array matching pattern `" .. pat .. "'")
return var
end end
function need_array_of(varname, array, required) function need_array_of(path, array, required)
local ok, var = pcall(need_array, varname, function(e) assert_one_of(e, array) end,required) return need_array(path, function(e) need_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
end end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment