Added a uci module

This commit is contained in:
Simon Schuster 2016-09-02 21:57:24 +02:00
parent f279c08267
commit f2be7d2b04

305
src/uci.lua Normal file
View file

@ -0,0 +1,305 @@
#!/usr/bin/lua
-- WANT_JSON
local Ansible = require("ansible")
local ubus = require("ubus")
function reload_configs(module)
local conn = module:ubus_connect()
local res = module:ubus_call(conn, "uci", "reload_config", {})
conn:close()
module:exit_json({msg="Configs reloaded", result=res})
end
function get_configs(module)
local conn = module:ubus_connect()
local res = module:ubus_call(conn, "uci", "configs", {})
conn:close()
module:exit_json({msg="Configs fetched", result=res})
end
function docommit(module, conn, config)
local conf, sec = check_config(module, conn, config, nil)
local res = module:ubus_call(conn, "uci", "commit", {config=conf})
return res
end
function commit(module)
local conn = module:ubus_connect()
local path = module:get_params()["name"]
if path["option"] or path["section"] then
module:fail_json({msg="Only whole configs can be committed"})
end
local res = docommit(module, conn, path["config"])
module:exit_json({msg="Committed all changes for config " .. path["config"], changed=true, result=res})
end
function revert(module)
local conn = module:ubus_connect()
local path = module:get_params()["name"]
local conf, sec = check_config(module, conn, path["config"], nil)
local res = module:ubus_call(conn, "uci", "revert", {config=conf})
module:exit_json({msg="Successfully reverted all staged changes to " .. conf, changed=true, result=res})
end
function parse_path(module)
local path = module:get_params()['name']
-- a path consists of config.section.option
-- lua's pattern engine does not seem to be expressive enough to do this in one go
local config, section, option
if string.match(path, "([^.]+)%.([^.]+)%.([^.]+)") then
config, section, option = string.match(path, "([^.]+)%.([^.]+)%.([^.]+)")
elseif string.match(path, "([^.]+)%.([^.]+)") then
config, section = string.match(path, "([^.]+)%.([^.]+)")
else
config = path
end
local pathobject = {config=config, section=section, option=option}
return pathobject
end
function query_value(module, conn, path, unique)
local res = conn:call("uci", "get", path)
if nil == res then
return nil
end
if unique and nil ~= res["values"] then
module:fail_json({msg="Path specified is amiguos and matches multiple options", path=path, result=res})
end
if res["values"] then
return res["values"]
else
return res["value"]
end
end
function check_config(module, conn, config, section)
local res = module:ubus_call(conn, "uci", "configs", {})
if not module.contains(config, res["configs"]) then
module:fail_json({msg="Invalid config " .. config})
end
if nil ~= section then
res = module:ubus_call(conn, "uci", "get", {config=config, section=section})
if res and res["values"] and res["values"][".type"] then
return config, section
end
end
return config, nil
end
function set_value(module)
local p = module:get_params()
local path = p["name"]
local conn = module:ubus_connect()
local conf, sec = check_config(module, conn, path["config"], path["section"])
local target = p["value"]
local is = query_value(module, conn, path, true)
local values = {}
if path["option"] then
values[path["option"]] = p["value"]
end
local res
if not sec then
-- We have to create a section and use "uci add"
if not p["type"] then
module:fail_json({msg="when creating sections, a type is required", message=message})
end
local message = {
config=conf,
name=path["section"],
type=p["type"],
}
if path["option"] then
values[path["option"]] = p["value"]
message["values"]=values
end
res = module:ubus_call(conn, "uci", "add", message)
elseif target ~= is then
-- We have to take actions and use "uci set"
local message = {
config=conf,
section=sec,
values=values
}
res = module:ubus_call(conn, "uci", "set", message)
else
conn:close()
module:exit_json({msg="Value already set", changed=false, result=res})
end
local autocommit = false
if p["autocommit"] then
autocommit = true
docommit(module, conn, conf)
end
conn:close()
module:exit_json({msg="Value successfully set", changed=true, autocommit=autocommit, result=res})
end
function unset_value(module)
local p = module:get_params()
local path = p["name"]
local conn = module:ubus_connect()
local conf, sec = check_config(module, conn, path["config"], path["section"])
-- the whole section is already gone
if nil == sec then
-- already absent
conn:close()
module:exit_json({msg="Section already absent", changed=false})
end
-- and nil ~= sec...
local message = {
config=conf,
section=sec
}
-- check if we have got a option
if path["option"] then
local is = query_value(module, conn, path, false)
if not is then
conn:close()
module:exit_json({msg="Option already absent", changed=false})
end
message["option"] = path["option"]
end
local res = module:ubus_call(conn, "uci", "delete", message)
local autocommit = false
if p["autocommit"] then
local autocommit = true
docommit(module, conn, conf)
end
conn:close()
module:exit_json({msg="Section successfully deleted", changed=true, autocommit=autocommit, result=res})
end
function check_parameters(module)
local p = module:get_params()
-- Validate the path
if p["name"] then
p["name"] = parse_path(module, p["name"])
end
-- op requires that no state is given, configs does not take any parameter
if p["op"] then
-- all operands do not take a state or value parameter
if p["value"] then
module:fail_json({msg="op=* do not work with 'state','value' or 'autocommit' arguments"})
end
-- config does not take a path parameter
if "configs" == p["op"] and p["name"] then
module:fail_json({msg="'op=config' does not take a 'path' argument"})
end
else
-- in the normal case name and state are required
if (not p["name"])
or (not p["state"]) then
module:fail_json({msg="Both name and state are required to set/unset values"})
end
-- when performing an "uci set", a value is required
if ("set" == p["state"] or "present" == p["state"]) then
if p["name"]["option"] and not p["value"] then -- Setting a regular value
module:fail_json({msg="When using 'uci set', a value is required"})
elseif not p["name"]["option"] and not p["type"] then -- Creating a section
module:fail_json({msg="When creating sections with 'uci set', a type is required"})
end
end
if nil ~= p["value"] and ("unset" == p["state"] or "absent" == p["state"]) then
module:fail_json({msg="When deleting options, no value can be set"})
end
end
end
function main(arg)
local module = Ansible.new({
name = { aliases = {"path", "key"}, type="str"},
value = { type="str" },
state = { default="present", choices={"present", "absent", "set", "unset"} },
op = { choices={"configs", "commit", "revert"} },
reload = { aliases = {"reload_configs", "reload-configs"}, type='bool'},
autocommit = { default=true, type="bool" },
type = { type="str" },
socket = { type="path" },
timeout = { type="int"}
})
module:parse(arg[1])
check_parameters(module)
local p = module:get_params()
if p["reload"] then
reload_configs(module)
end
-- Execute operation
if "configs" == p["op"] then
get_configs(module)
elseif "commit" == p["op"] then
commit(module)
elseif "revert" == p["op"] then
revert(module)
else
-- If no op was given, simply enforce the setting state
local state = p["state"]
-- check if a full path was specified
local path = p["name"]
if not path["config"] or not path["section"] then
module:fail_json({msg="Path has to be structured like this: '<config>.<section>[.<option>]'", parsed=pathobject})
end
-- Do the ops
if "present" == state or "set" == state then
set_value(module)
elseif "absent" == state or "removed" == state then
unset_value(module)
end
end
end
main(arg)