385 lines
11 KiB
Bash
385 lines
11 KiB
Bash
#!/bin/sh
|
|
# Copyright (c) 2017 Markus Weippert
|
|
# GNU General Public License v3.0 (see https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
WANT_JSON="1"
|
|
PARAMS="
|
|
autocommit/bool
|
|
command=cmd/str
|
|
config/str
|
|
find=find_by=search/any
|
|
keep_keys=keep/any
|
|
key/str
|
|
merge/bool//false
|
|
name/str
|
|
option/str
|
|
replace/bool//false
|
|
section/str
|
|
set_find/bool//true
|
|
type/str
|
|
unique/bool//false
|
|
value/any
|
|
"
|
|
RESPONSE_VARS="result=_result command config section option"
|
|
|
|
init() {
|
|
state_path=""
|
|
[ -z "$_ansible_check_mode" ] || state_path="$(mktemp -d)" ||
|
|
fail "could not create state path"
|
|
changes="$(uci_change_hash)"
|
|
case "$_type_keep_keys" in
|
|
object) fail "keep_keys must be list or string";;
|
|
array) json_get_values keep_keys "$_keep_keys" || :;;
|
|
esac
|
|
[ -z "$key" ] &&
|
|
key="${config:+$config${section:+.$section${option:+.$option}}}" ||
|
|
{ oIFS="$IFS"; IFS="."; set -- $key; IFS="$oIFS"
|
|
config="$1"; section="$2"; option="$3"; }
|
|
[ -z "$_ansible_diff" -o -z "$config" ] ||
|
|
set_diff "$(uci export "$config")"
|
|
[ -n "$command" ] || { [ -z "$value" ] && command="get" || command="set"; }
|
|
}
|
|
|
|
uci() {
|
|
[ -z "$state_path" ] || set -- -P "$state_path" "$@"
|
|
command uci "$@"
|
|
}
|
|
|
|
uci_change_hash() {
|
|
uci changes | md5
|
|
}
|
|
|
|
uci_result_do() {
|
|
json_set_namespace result
|
|
"$@"
|
|
json_set_namespace params
|
|
}
|
|
|
|
uci_get_safe() {
|
|
local tmp opts
|
|
while [ "${1#-}" != "$1" ]; do opts="$opts $1"; shift; done
|
|
tmp="$(uci $opts show "$1")" || return $?
|
|
echo "${tmp#*=}"
|
|
}
|
|
|
|
uci_check_type() {
|
|
local key="$1"; local type="$2"; local t
|
|
t="$(uci -q get "$key")" || return 1
|
|
[ -n "$type" -a "$t" != "$type" ] || return 0
|
|
fail "$key exists with $t instead of $type"
|
|
}
|
|
|
|
uci_compare_list() {
|
|
local k="${1:-$key}"
|
|
local match="1"
|
|
local keys values v i
|
|
json_get_keys keys
|
|
! values="$(uci_get_safe -q "$k")" || {
|
|
eval "set -- $values"
|
|
for i in $keys; do
|
|
json_get_var v "$i"
|
|
[ $# -gt 0 -a "$v" = "$1" ] || { match=""; break; }
|
|
shift
|
|
done
|
|
[ $# -eq 0 ] || match=""
|
|
[ -z "$match" ] || return 0
|
|
}
|
|
return 1
|
|
}
|
|
|
|
uci_add() {
|
|
section="${section:-$value}"
|
|
[ -n "$name" -o -z "$type" ] || name="$section"
|
|
type="${type:-$section}"
|
|
[ -n "$type" ] || fail "type required for $command"
|
|
[ -n "$name" ] && {
|
|
uci_check_type "$config.$name" "$type" || {
|
|
try uci add "$config" "$type"
|
|
try uci rename "$config.$_result=$name"
|
|
}
|
|
} || try uci add "$config" "$type"
|
|
}
|
|
|
|
uci_set_list() {
|
|
local k="${1:-$key}"
|
|
local keys v i
|
|
! uci_compare_list "$k" || return 0
|
|
uci -q delete "$k" || :
|
|
json_get_keys keys
|
|
for i in $keys; do
|
|
json_get_var v "$i"
|
|
try uci add_list "$k=$v"
|
|
done
|
|
}
|
|
|
|
uci_set_dict() {
|
|
local keys k v t
|
|
json_get_keys keys
|
|
keep_keys="$keep_keys $keys"
|
|
for k in $keys; do
|
|
json_get_type t "$k"
|
|
case "$t" in
|
|
array)
|
|
json_select "$k"
|
|
uci_set_list "$key.$k"
|
|
json_select ..;;
|
|
object) fail "cannot set $k to dict";;
|
|
*)
|
|
json_get_var v "$k"
|
|
try uci set "$key.$k=$v";;
|
|
esac
|
|
done
|
|
}
|
|
|
|
uci_set() {
|
|
local var="${1:-value}"
|
|
local var_type
|
|
eval "var_type=\"\$_type_$var\""
|
|
[ -z "$option" ] || keep_keys="$keep_keys $option"
|
|
case "$var_type" in
|
|
array)
|
|
[ -n "$config" -a -n "$section" -a -n "$option" ] ||
|
|
fail "config, section and option required for $command"
|
|
json_select_real "$var"
|
|
uci_set_list
|
|
json_select ..;;
|
|
object)
|
|
[ -n "$config" -a -n "$section" -a -z "$option" ] ||
|
|
fail "config and section but not option required for $command"
|
|
json_select_real "$var"
|
|
uci_set_dict
|
|
json_select ..;;
|
|
*) try "uci set \"\$key=\$$var\"";;
|
|
esac
|
|
}
|
|
|
|
uci_get() {
|
|
local entry
|
|
try uci get "$key"
|
|
eval "set -- $(uci_get_safe -q "$key")"
|
|
json_set_namespace result
|
|
json_add_array result_list
|
|
for entry; do json_add_string . "$entry"; done
|
|
json_close_array
|
|
json_set_namespace params
|
|
}
|
|
|
|
uci_find() {
|
|
local keys i c v k tmp
|
|
case "$_type_find" in
|
|
array|object)
|
|
[ -n "$config" -a -n "$type" ] ||
|
|
fail "config and type required for $command"
|
|
json_select_real find
|
|
json_get_keys keys;;
|
|
*)
|
|
[ -n "$config" -a -n "$type" ] &&
|
|
[ -n "$option" -o "$command" = find_all ] ||
|
|
fail "config, type and option required for $command";;
|
|
esac
|
|
[ "$command" != "find_all" ] || uci_result_do json_add_array result
|
|
type="${type:-$section}"
|
|
section=""; i=0
|
|
while [ -n "$(uci -q get "$config.@$type[$i]")" ]; do
|
|
c="@$type[$((i++))]"
|
|
case "$_type_find" in
|
|
array)
|
|
[ -z "$option" ] && {
|
|
for k in $keys; do
|
|
json_get_var v "$k"
|
|
uci -q get "$config.$c.$v" >/dev/null || continue 2
|
|
done
|
|
} || {
|
|
uci_compare_list "$config.$c.$option" || continue
|
|
};;
|
|
object)
|
|
for k in $keys; do
|
|
json_get_type tmp "$k"
|
|
case "$tmp" in
|
|
array)
|
|
json_select "$k"
|
|
uci_compare_list "$config.$c.$k" &&
|
|
tmp="1" || tmp=""
|
|
json_select ..
|
|
[ -n "$tmp" ] || continue 2;;
|
|
object)
|
|
fail "cannot compare $k with dict";;
|
|
*)
|
|
json_get_var v "$k"
|
|
tmp="$(uci -q get "$config.$c.$k")" &&
|
|
[ "$tmp" = "$v" ] || continue 2
|
|
esac
|
|
done;;
|
|
*)
|
|
[ -z "$option" ] || {
|
|
v="$(uci -q get "$config.$c.$option")" &&
|
|
[ -z "$find" -o "$find" = "$v" ] || continue
|
|
};;
|
|
esac
|
|
[ "$command" = "find_all" ] || { section="$c"; break; }
|
|
uci_result_do json_add_string . "$c"
|
|
done
|
|
case "$_type_find" in
|
|
array|object) json_select ..;;
|
|
esac
|
|
case "$command" in
|
|
find_all)
|
|
uci_result_do json_close_array
|
|
_result="";;
|
|
*)
|
|
_result="$section"
|
|
[ -n "$section" ] && return 0 || return 1;;
|
|
esac
|
|
}
|
|
|
|
uci_ensure() {
|
|
local keys k v
|
|
[ -n "$name" -o -z "$type" ] || name="$section"
|
|
type="${type:-$section}"
|
|
[ -n "$config" -a -n "$type" ] ||
|
|
fail "config, type and name required for $command"
|
|
[ -n "$name" ] && uci_check_type "$config.$name" "$type" || {
|
|
[ "$_type_find" = "object" -o -n "$option" ] && uci_find && {
|
|
[ -z "$name" ] || try uci rename "$config.$section=$name"
|
|
} || {
|
|
[ "$command" = absent ] && return 0 || uci_add
|
|
}
|
|
}
|
|
section="${name:-$_result}"
|
|
key="$config.$section${option:+.$option}"
|
|
[ "$command" = "absent" ] && {
|
|
[ -z "$_defined_value" ] &&
|
|
{ uci -q delete "$key" || :; } ||
|
|
case "$_type_value" in
|
|
array|object)
|
|
json_select_real value
|
|
json_get_keys keys
|
|
for k in $keys; do
|
|
json_get_var v "$k"
|
|
case "$_type_value" in
|
|
array) uci -q delete "$config.$section.$v";;
|
|
object) uci -q delete "$config.$section.$k=$v";;
|
|
esac
|
|
done
|
|
json_select ..;;
|
|
*) uci -q delete "$config.$section.$value";;
|
|
esac
|
|
return 0
|
|
}
|
|
[ -z "$set_find" -o "$_type_find" != "object" -a -z "$option" ] || {
|
|
uci_set find
|
|
[ "$_type_value" != "object" ] || {
|
|
key="$config.$section"
|
|
option=""
|
|
}
|
|
}
|
|
[ -z "$_defined_value" ] || uci_set
|
|
_result="$section"
|
|
}
|
|
|
|
uci_cleanup_section() {
|
|
case "$command" in
|
|
set|ensure|section) :;;
|
|
*) return 0;;
|
|
esac
|
|
local k v
|
|
[ -n "$replace" -a -n "$config" -a -n "$section" ] || return 0
|
|
for k in $(uci -q show "$config.$section" |
|
|
sed -n 's/^..*\...*\.\([^.][^.]*\)=.*$/\1/p')
|
|
do
|
|
for v in $keep_keys; do
|
|
[ "$k" != "$v" ] || continue 2
|
|
done
|
|
uci -q delete "$config.$section.$k"
|
|
done
|
|
}
|
|
|
|
uci_revert() {
|
|
[ -z "$key" ] && rm -f -- "/tmp/.uci"/* 2>/dev/null || uci revert "$key"
|
|
}
|
|
|
|
uci_autocommit() {
|
|
[ -n "$autocommit" ] || return 0
|
|
case "$command" in
|
|
commit|revert) return 0;;
|
|
esac
|
|
[ "$1" -eq 0 ] && {
|
|
[ -n "$config" ] && uci commit "$config" || uci commit
|
|
} || uci_revert
|
|
}
|
|
|
|
main() {
|
|
case "$command" in
|
|
batch|import)
|
|
[ -n "$value" ] || fail "value required for $command";;
|
|
add_list|del_list|rename|reorder)
|
|
[ -n "$key" -a -n "$value" ] ||
|
|
fail "key and value required for $command";;
|
|
add|get|delete|ensure|absent)
|
|
[ -n "$key" ] || fail "key required for $command";;
|
|
esac
|
|
case "$command" in
|
|
batch)
|
|
echo "$value" | final uci $command;;
|
|
export|changes|show|commit)
|
|
local cmd="final uci $command"
|
|
[ -z "$key" ] && $cmd || $cmd "$key";;
|
|
import)
|
|
local cmd="final uci ${merge:+-m }$command"
|
|
[ -z "$key" ] && echo "$value" | $cmd ||
|
|
echo "$value" | $cmd "$key";;
|
|
add)
|
|
uci_add; exit 0;;
|
|
add_list)
|
|
[ -z "$unique" ] || {
|
|
eval "set -- $(uci_get_safe -q "$key")"
|
|
for entry; do [ "$entry" = "$value" ] && exit 0; done
|
|
}
|
|
final uci add_list "$key=$value";;
|
|
del_list|reorder)
|
|
final uci $command "$key=$value";;
|
|
get)
|
|
uci_get; exit 0;;
|
|
delete)
|
|
final uci $command "$key${value:+=$value}";;
|
|
rename)
|
|
final uci $command "$key=${name:-$value}";;
|
|
revert)
|
|
final uci_revert;;
|
|
set)
|
|
uci_set; exit 0;;
|
|
find|find_all)
|
|
uci_find; exit $?;;
|
|
ensure|section)
|
|
uci_ensure; exit 0;;
|
|
absent)
|
|
[ -n "$_defined_find" ] || {
|
|
uci -q delete "$key${value:+=$value}"; exit 0
|
|
}
|
|
uci_ensure; exit 0;;
|
|
*) fail "unknown command: $command";;
|
|
esac
|
|
}
|
|
|
|
cleanup() {
|
|
local ec="$1"
|
|
[ "$ec" -ne 0 ] || uci_cleanup_section
|
|
[ "$changes" = "$(uci_change_hash)" ] || {
|
|
changed
|
|
[ "$_ansible_verbosity" -lt 2 ] || {
|
|
local _IFS line
|
|
_IFS="$IFS"; IFS="$N"; set -- $(uci changes); IFS="$_IFS"
|
|
json_set_namespace result
|
|
json_add_array changes
|
|
for line; do json_add_string . "$line"; done
|
|
json_close_array
|
|
json_set_namespace params
|
|
}
|
|
uci_autocommit "$ec"
|
|
}
|
|
[ -z "$_ansible_diff" -o -z "$config" ] ||
|
|
set_diff "" "$(uci export "$config")"
|
|
[ -z "$_ansible_check_mode" -o -z "$state_path" -o ! -d "$state_path" ] ||
|
|
rm -rf "$state_path"
|
|
}
|