From 22bcbb825ac312c1be582ecce154e399004ecb5d Mon Sep 17 00:00:00 2001 From: Nick Cabatoff Date: Mon, 2 Oct 2017 10:44:06 -0400 Subject: [PATCH] Add new options 'match' and 'values'. Add new op 'get'. New set mode: set path=config match={} values={} Find uci sections based on path and match, modify their properties according to values. # Modify settings for pre-existing wireless interface with device radio0. - name: configure wireless uci: path: wireless match: device: radio0 type: wifi-iface values: ssid: myssid encryption: psk2 key: "secret passphrase" New op: get path=config[.section] [type=t] [match=m] Used with register var x, outputs to x.result['values'] (x.result.values won't work.) This can be used for conditions in subsequent tasks. # See if there already exists a forwarding config with dest=myvpn and src=lan - name: Get myvpn zone forwarding uci: op=get path=firewall type=forwarding match="dest=myvpn src=lan" register: myvpnfw # Create the section if it doesn't exist - name: Add myvpn zone forwarding uci: autocommit=false path=firewall type=forwarding when: not myvpnfw.result['values'] # Populate the section if it didn't exist - name: Setup myvpn zone forwarding uci: autocommit=false path=firewall.@forwarding[-1].{{ item.key }} value={{ item.value}} with_dict: dest: myvpn src: lan when: not myvpnfw.result['values'] --- README.md | 6 ++-- src/uci.lua | 79 +++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 68 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 2ee7e62..8e871d9 100644 --- a/README.md +++ b/README.md @@ -179,12 +179,14 @@ input. If you need to force a singleentry list, please be sure to set the |-----------|----------|---------|-----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | name | no | | | Path to the property to change. Syntax is `config.section.option`. _Aliases: path, key_ | | value | no | | | For set: value to set the property to | +| match | no | | | When present in a set or get op: properties a section must have to be modified or returned | +| values | no | | | For set with match: values to set on matching section | | forcelist | no | false | Boolean | The module trys to guess the uci config type (list or string) from the supplied value via the existance of `,` in the input. Single entry lists require `forcelist=yes` to be recognized correctly | | state | no | present | present, absent, set, unset | State of the property | -| op | no | | configs, commit, revert | If specified, instead of enforcing a value, either list the available configurations, or execute a commit/revert operation | +| op | no | | configs, commit, revert, get| If specified, instead of enforcing a value, either list the available configurations, execute a commit/revert operation, or query properties. | | reload | no | | Boolean | Whether to reload the configuration from disk before executing. _Aliases: reload_configs, reload-configs_ | | autocomit | no | true | Boolean | Whether to automatically commit the changes made | -| type | no | | | When creating a new section, a configuration-type is required. Aliases: _section-type_ | +| type | no | | | When creating a new section, a configuration-type is required. Can also be used to qualify a get. Aliases: _section-type_ | | socket | no | | | Set a nonstandard path to the ubus socket if necessary | | timeout | no | | | Change the default ubus timeout | diff --git a/src/uci.lua b/src/uci.lua index 5e1bf16..ad348df 100644 --- a/src/uci.lua +++ b/src/uci.lua @@ -54,6 +54,27 @@ function commit(module) module:exit_json({msg="Committed all changes for " .. #configs .. " configurations", changed=true, result=res}) end +function get(module) + local conn = module:ubus_connect() + local p = module:get_params() + local path = p["name"] + + local msg = {config=path["config"]} + if p["match"] ~= nil then + msg["match"] = p["match"] + end + if p["type"] ~= nil then + msg["type"] = p["type"] + end + if path["section"] ~= nil then + msg["section"] = path["section"] + end + + local res = module:ubus_call(conn, "uci", "get", msg) + + module:exit_json({msg="Got config", changed=false, result=res}) +end + function revert(module) local conn = module:ubus_connect() local path = module:get_params()["name"] @@ -129,6 +150,10 @@ function check_config(module, conn, config, section) end function compare_tables(a, b) + if a == nil or b == nil then + return a == b + end + if type(a) ~= "table" then if type(b) ~= "table" then return a == b @@ -138,9 +163,6 @@ function compare_tables(a, b) if #a ~= #b then return false end - if a == nil or b == nil then - return a == b - end -- level 1 compare table.sort(a) table.sort(b) @@ -162,7 +184,6 @@ function set_value(module) local conf, sec = check_config(module, conn, path["config"], path["section"]) local target = p["value"] - local is = query_value(module, conn, path, true) local forcelist = p["forcelist"] if type(target) == "table" and #target == 1 and not forcelist then @@ -175,7 +196,16 @@ function set_value(module) end local res - if not sec then + if nil ~= p["match"] then + local message = { + config=conf, + values=p["values"], + match=p["match"] + } + -- TODO: how can we properly report whether a changed happend in this case? + -- Get the entire config before and after to compare? + res = module:ubus_call(conn, "uci", "set", message) + elseif 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}) @@ -193,7 +223,7 @@ function set_value(module) res = module:ubus_call(conn, "uci", "add", message) - elseif not compare_tables(target, is) then + elseif not compare_tables(target, query_value(module, conn, path, true)) then -- We have to take actions and use "uci set" local message = { config=conf, @@ -292,7 +322,7 @@ function check_parameters(module) 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 + elseif not p["name"]["option"] and not p["type"] and not p["match"] then -- Creating a section module:fail_json({msg="When creating sections with 'uci set', a type is required"}) end end @@ -313,13 +343,15 @@ function main(arg) name = { aliases = {"path", "key"}, type="str"}, value = { type="list" }, state = { default="present", choices={"present", "absent", "set", "unset"} }, - op = { choices={"configs", "commit", "revert"} }, + op = { choices={"configs", "commit", "revert", "get"} }, reload = { aliases = {"reload_configs", "reload-configs"}, type='bool'}, autocommit = { default=true, type="bool" }, forcelist = { default=false, type="bool" }, type = { aliases = {"section-type"}, type="str" }, socket = { type="path" }, - timeout = { type="int"} + timeout = { type="int"}, + match = { type="dict"}, + values = { type="dict"} }) module:parse(arg[1]) @@ -338,20 +370,37 @@ function main(arg) commit(module) elseif "revert" == p["op"] then revert(module) + elseif "get" == p["op"] then + get(module) else -- If no op was given, simply enforce the setting state - local state = p["state"] + local state = p["state"] + local doset = true + if "absent" == state or "unset" == state then + doset = false + elseif "present" ~= state and "set" ~= state then + module:fail_json({msg="Set state must be one of set, present, unset, absent"}) + end -- 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: '.
[.