From f2be7d2b042f407642613770ba2d8891e5a517a6 Mon Sep 17 00:00:00 2001 From: Simon Schuster Date: Fri, 2 Sep 2016 21:57:24 +0200 Subject: [PATCH] Added a uci module --- src/uci.lua | 305 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 src/uci.lua diff --git a/src/uci.lua b/src/uci.lua new file mode 100644 index 0000000..d5f358a --- /dev/null +++ b/src/uci.lua @@ -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: '.
[.