gluon-config-api: First working version

Fix bugs, ...
This commit is contained in:
lemoer 2021-08-11 14:54:07 +02:00
parent 60aa73126e
commit 64ed50c44a
4 changed files with 177 additions and 22 deletions

View File

@ -6,7 +6,8 @@ local os = require 'os'
local glob = require 'posix.glob'
local libgen = require 'posix.libgen'
local simpleuci = require 'simple-uci'
local schema = require 'schema'
local schema = dofile('../controller/schema.lua') -- pwd is www/
local ucl = require "ucl"
package 'gluon-config-api'
@ -29,20 +30,97 @@ function config_get(parts)
return config
end
local parts = load_parts()
entry({"config"}, call(function(http, renderer)
http:write(json.stringify(config_get(parts), true))
http:close()
end))
entry({"schema"}, call(function(http, renderer)
function schema_get(parts)
local total_schema = {}
for _, part in pairs(parts) do
total_schema = schema.merge_schemas(total_schema, part.schema())
total_schema = schema.merge_schemas(total_schema, part.schema(site, nil))
end
http:write(json.stringify(total_schema, true))
return total_schema
end
function config_set(parts, config)
local uci = simpleuci.cursor()
for _, part in pairs(parts) do
part.set(config, uci)
end
end
local function pump(src, snk)
while true do
local chunk, src_err = src()
local ret, snk_err = snk(chunk, src_err)
if not (chunk and ret) then
local err = src_err or snk_err
if err then
return nil, err
else
return true
end
end
end
end
local parts = load_parts()
entry({"v1", "config"}, call(function(http, renderer)
if http.request.env.REQUEST_METHOD == 'GET' then
http:write(json.stringify(config_get(parts), true))
elseif http.request.env.REQUEST_METHOD == 'POST' then
local request_body = ""
pump(http.input, function (data)
if data then
request_body = request_body .. data
end
end)
-- Verify that we really have JSON input. UCL is able to parse other
-- config formats as well. Those config formats allow includes and so on.
-- This may be a security issue.
local config = json.parse(request_body)
if not config then
http:status(400, 'Bad Request')
http:write('{ "status": 400, "error": "Bad JSON in Body" }\n')
http:close()
return
end
-- Verify schema
local parser = ucl.parser()
local res, err = parser:parse_string(request_body)
if not res then
http:status(500, 'Internal Server Error.')
http:write('{ "status": 500, "error": "Internal UCL Parsing Failed. This should not happen at all." }\n')
http:close()
return
end
res, err = parser:validate(schema_get(parts))
if not res then
http:status(400, 'Bad Request')
http:write('{ "status": 400, "error": "Schema mismatch" }\n')
http:close()
return
end
-- Apply config
config_set(parts, config)
http:write(json.stringify(res, true))
else
http:status(400, 'Bad Request')
http:write('Not implemented')
end
http:close()
end))
entry({"v1", "schema"}, call(function(http, renderer)
http:write(json.stringify(schema_get(parts), true))
http:close()
end))

View File

@ -34,12 +34,12 @@ local function merged_keys(table1, table2)
local keys = {}
if table1 then
for k, _ in pairs(table1) do
table.insert(k)
table.insert(keys, k)
end
end
if table2 then
for k, _ in pairs(table2) do
if not util.contains(k) then
if not util.contains(keys, k) then
table.insert(keys, k)
end
end
@ -51,12 +51,12 @@ local function merged_values(table1, table2)
local values = {}
if table1 then
for _, v in pairs(table1) do
table.insert(v)
table.insert(values, v)
end
end
if table2 then
for _, v in pairs(table2) do
if not util.contains(v) then
if not util.contains(values, v) then
table.insert(values, v)
end
end
@ -105,7 +105,7 @@ function M.merge_schemas(schema1, schema2)
local pdef2 = properties2[pkey]
if pdef1 and pdef2 then
add_property(pkey, merge_schemas(pdef1, pdef2))
add_property(pkey, M.merge_schemas(pdef1, pdef2))
elseif pdef1 then
add_property(pkey, deepcopy(pdef1))
elseif pdef2 then
@ -115,14 +115,14 @@ function M.merge_schemas(schema1, schema2)
-- generate merged.additionalProperties
if schema1.additionalProperties and schema2.additionalProperties then
merged.additionalProperties = merge_schemas(
merged.additionalProperties = M.merge_schemas(
schema1.additionalProperties, schema2.additionalProperties)
else
merged.additionalProperties = false
end
-- generate merged.required
merged.required = merged_values(schema1, schema2)
merged.required = merged_values(schema1.required, schema2.required)
if #merged.required == 0 then
merged.required = nil
end
@ -132,6 +132,8 @@ function M.merge_schemas(schema1, schema2)
-- generate merged.default
merged.default = schema2.default or schema1.default
return merged
end
return M

View File

@ -13,11 +13,12 @@ function M.schema(site, platform)
}
}
}
}
},
required = { 'wizard' }
}
end
function M.save(config, uci)
function M.set(config, uci)
local owner = uci:get_first("gluon-node-info", "owner")
uci:set("gluon-node-info", owner, "contact", config.wizard.contact)

View File

@ -0,0 +1,74 @@
local M = {}
function M.schema(site, platform)
local altitude = nil
if site.config_mode.geo_location.show_altitude(false) then
altitude = { type = 'number' }
end
return {
type = 'object',
properties = {
wizard = {
type = 'object',
properties = {
location = {
type = 'object',
properties = {
share_location = { type = 'boolean' },
lat = { type = 'number' },
lon = { type = 'number' },
altitude = altitude
},
required = { 'lat', 'lon', 'share_location' }
}
}
}
},
required = { 'wizard' }
}
end
function M.set(config, uci)
local location = uci:get_first("gluon-node-info", "location")
local config_location = config.wizard.location
if config_location then
uci:set("gluon-node-info", location, "share_location", config_location.share_location)
uci:set("gluon-node-info", location, "latitude", config_location.lat)
uci:set("gluon-node-info", location, "longitude", config_location.lon)
if config_location.altitude then -- altitude is optional
uci:set("gluon-node-info", location, "altitude", config_location.altitude) -- TODO: check if the "if" is necessary
else
uci:delete("gluon-node-info", location, "altitude")
end
else
uci:set("gluon-node-info", location, "share_location", false)
uci:delete("gluon-node-info", location, "latitude")
uci:delete("gluon-node-info", location, "longitude")
uci:delete("gluon-node-info", location, "altitude")
end
uci:save("gluon-node-info")
end
function M.get(uci, config)
config.wizard = config.wizard or {}
local location = uci:get_first("gluon-node-info", "location")
local lon = uci:get("gluon-node-info", location, "longitude")
if lon then
config.wizard.location = {
share_location = uci:get_bool("gluon-node-info", location, "share_location"),
lat = tonumber(uci:get("gluon-node-info", location, "latitude")),
lon = tonumber(lon),
-- if uci:get() returns nil, then altitude will not be present in the result
altitude = tonumber(uci:get("gluon-node-info", location, "altitude"))
}
end
end
return M