diff --git a/cdist/conf/type/__uci/explorer/state b/cdist/conf/type/__uci/explorer/state new file mode 100644 index 00000000..d7363dbf --- /dev/null +++ b/cdist/conf/type/__uci/explorer/state @@ -0,0 +1,110 @@ +#!/bin/sh +# +# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# +# This explorer retrieves the current state of the configuration option +# The output of this explorer is one of these values: +# present +# The configuration option is present and has the value of the +# parameter --value. +# absent +# The configuration option is not defined. +# different +# The configuration option is present but has a different value than the +# parameter --value. +# rearranged +# The configuration option is present (a list) and has the same values as +# the parameter --value, but in a different order. + +RS=$(printf '\036') + +option=${__object_id:?} + +values_is=$(uci -s -N -d "${RS}" get "${option}" 2>/dev/null) || { + echo absent + exit 0 +} + +if test -f "${__object:?}/parameter/value" +then + should_file="${__object:?}/parameter/value" +else + should_file='/dev/null' +fi + + +# strip off trailing newline +printf '%s' "${values_is}" \ +| awk ' +function unquote(s) { + # simplified dequoting of single quoted strings + if (s ~ /^'\''.*'\''$/) { + s = substr(s, 2, length(s) - 2) + sub(/'"'\\\\''"'/, "'\''", s) + } + return s +} + +BEGIN { + state = "present" # assume all is fine +} +NR == FNR { + # memoize "should" state + should[FNR] = $0 + should_count++ + + # go to next line (important!) + next +} + +# compare "is" state + +{ $0 = unquote($0) } + +$0 == should[FNR] { next } + +FNR > should_count { + # there are more "is" records than "should" -> definitely different + state = "different" + exit +} + +{ + # see if we can find the value somewhere in should + for (i in should) { + if ($0 == should[i]) { + # ... value found -> rearranged + # FIXME: Duplicate values are not properly handled here. Do they matter? + state = "rearranged" + next + } + } + + state = "different" + exit +} + +END { + if (FNR < should_count) { + # "is" was shorter than "should" -> different + state = "different" + } + + print state +} +' "${should_file}" RS="${RS}" - diff --git a/cdist/conf/type/__uci/files/functions.sh b/cdist/conf/type/__uci/files/functions.sh new file mode 100644 index 00000000..277f648c --- /dev/null +++ b/cdist/conf/type/__uci/files/functions.sh @@ -0,0 +1,73 @@ +# -*- mode: sh; indent-tabs-mode: t -*- + +in_list() { + printf '%s\n' "$@" | { grep -qxF "$(read -r ndl; echo "${ndl}")"; } +} + +quote() { + for _arg + do + shift + if test -n "$(printf %s "${_arg}" | tr -d -c '\t\n \042-\047\050-\052\073-\077\133\\`|~' | tr -c '' '.')" + then + # needs quoting + set -- "$@" "$(printf "'%s'" "$(printf %s "${_arg}" | sed -e "s/'/'\\\\''/g")")" + else + set -- "$@" "${_arg}" + fi + done + unset _arg + + # NOTE: Use printf because POSIX echo interprets escape sequences + printf '%s' "$*" +} + +uci_cmd() { + # Usage: uci_cmd [UCI ARGUMENTS]... + mkdir -p "${__object:?}/files" + printf '%s\n' "$(quote "$@")" >>"${__object:?}/files/uci_batch.txt" +} + +uci_validate_name() { + # like util.c uci_validate_name() + test -n "$*" && test -z "$(echo "$*" | tr -d '[:alnum:]_')" +} + +uci_validate_tuple() ( + tok=${1:?} + case $tok + in + (*.*.*) + # check option + option=${tok##*.} + uci_validate_name "${option}" || { + printf 'Invalid option: %s\n' "${option}" >&2 + return 1 + } + tok=${tok%.*} + ;; + (*.*) + # no option (section definition) + ;; + (*) + printf 'Invalid tuple: %s\n' "$1" >&2 + return 1 + ;; + esac + + case ${tok#*.} + in + (@*) section=$(expr "${tok#*.}" : '@\(.*\)\[-*[0-9]*\]$') ;; + (*) section=${tok#*.} ;; + esac + uci_validate_name "${section}" || { + printf 'Invalid section: %s\n' "${1#*.}" >&2 + return 1 + } + + config=${tok%%.*} + uci_validate_name "${config}" || { + printf 'Invalid config: %s\n' "${config}" >&2 + return 1 + } +) diff --git a/cdist/conf/type/__uci/files/uci_apply.sh b/cdist/conf/type/__uci/files/uci_apply.sh new file mode 100644 index 00000000..63f94290 --- /dev/null +++ b/cdist/conf/type/__uci/files/uci_apply.sh @@ -0,0 +1,43 @@ +changes=$(uci changes) + +if test -n "${changes}" +then + echo 'Uncommited UCI changes were found on the target:' + printf '%s\n\n' "${changes}" + echo 'This can be caused by manual changes or due to a previous failed run.' + echo 'Please investigate the situation, revert or commit the changes, and try again.' + exit 1 +fi >&2 + +check_errors() { + # reads stdin and forwards non-empty lines to stderr. + # returns 0 if stdin is empty, else 1. + ! grep -e . >&2 +} + +commit() { + uci commit +} + +rollback() { + printf '\nAn error occurred when trying to commit UCI transaction!\n' >&2 + + uci changes \ + | sed -e 's/^-//' -e 's/\..*\$//' \ + | sort -u \ + | while read -r _package + do + uci revert "${_package}" + echo "${_package}" # for logging + done \ + | awk ' + BEGIN { printf "Reverted changes in: " } + { printf "%s%s", (FNR > 1 ? ", " : ""), $0 } + END { printf "\n" }' >&2 + + return 1 +} + +uci_apply() { + uci batch 2>&1 | check_errors && commit || rollback +} diff --git a/cdist/conf/type/__uci/gencode-remote b/cdist/conf/type/__uci/gencode-remote new file mode 100755 index 00000000..348d15a1 --- /dev/null +++ b/cdist/conf/type/__uci/gencode-remote @@ -0,0 +1,101 @@ +#!/bin/sh -e +# +# 2020 Dennis Camera (dennis.camera@ssrq-sds-fds.ch) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# + +# shellcheck source=files/functions.sh +. "${__type:?}/files/functions.sh" + +state_is=$(cat "${__object:?}/explorer/state") +state_should=$(cat "${__object:?}/parameter/state") + +config=${__object_id:?} +uci_validate_tuple "${config}" + + +case ${state_should} +in + (present) + if in_list "${state_is}" 'present' 'rearranged' + then + # NOTE: order is ignored so rearranged is also fine. + exit 0 + fi + + # Determine type + type=$(cat "${__object:?}/parameter/type" 2>/dev/null || true) + case ${type} + in + (option|list) ;; + ('') + # Guess type by the number of values + test "$(wc -l "${__object:?}/parameter/value")" -gt 1 \ + && type=list \ + || type=option + ;; + (*) + printf 'Invalid --type: %s\n' "${type}" >&2 + exit 1 + ;; + esac + + case ${type} + in + (list) + printf 'set_list %s\n' "${config}" >>"${__messages_out:?}" + + if test "${state_is}" != 'absent' + then + uci_cmd delete "${config}" + fi + + while read -r value + do + uci_cmd add_list "${config}"="${value}" + done <"${__object:?}/parameter/value" + ;; + (option) + printf 'set %s\n' "${config}" >>"${__messages_out:?}" + + value=$(cat "${__object:?}/parameter/value") + uci_cmd set "${config}"="${value}" + ;; + esac + ;; + (absent) + if in_list "${state_is}" 'absent' + then + exit 0 + fi + + printf 'delete %s\n' "${config}" >>"${__messages_out:?}" + uci_cmd delete "${config}" + ;; + (*) + printf 'Invalid --state: %s\n' "${state_should}" >&2 + exit 1 + ;; +esac + +if test -s "${__object:?}/files/uci_batch.txt" +then + cat "${__type:?}/files/uci_apply.sh" + printf "uci_apply <<'EOF'\n" + cat "${__object:?}/files/uci_batch.txt" + printf '\nEOF\n' +fi diff --git a/cdist/conf/type/__uci/man.rst b/cdist/conf/type/__uci/man.rst new file mode 100644 index 00000000..81a53473 --- /dev/null +++ b/cdist/conf/type/__uci/man.rst @@ -0,0 +1,78 @@ +cdist-type__uci(7) +================== + +NAME +---- +cdist-type__uci - Manage configuration values in UCI + + +DESCRIPTION +----------- +This cdist type can be used to alter configuration options in OpenWrt's +Unified Configuration Interface (UCI) system. + + +REQUIRED PARAMETERS +------------------- +value + The value to be set. Can be used multiple times. + This parameter is ignored if ``--state`` is ``absent``. + + Due to the way cdist handles arguments, values **must not** contain newline + characters. + + Values do not need special quoting for UCI. The only requirement is that the + value is passed to the type as a single shell argument. + +OPTIONAL PARAMETERS +------------------- +state + ``present`` or ``absent``, defaults to ``present``. +type + If the type should generate an option or a list. + One of: ``option`` or ``list``. + Defaults to auto-detect based on the number of ``--value`` parameters. + + +BOOLEAN PARAMETERS +------------------ +None. + + +EXAMPLES +-------- + +.. code-block:: sh + + # Set the system hostname + __uci system.@system[0].hostname --value 'OpenWrt' + + # Set DHCP option 252: tell DHCP clients to not ask for proxy information. + __uci dhcp.lan.dhcp_option --type list --value '252,"\n"' + + # Enable NTP and NTPd (each is applied individually) + __uci system.ntp.enabled --value 1 + __uci system.ntp.enable_server --value 1 + __uci system.ntp.server --type list \ + --value '0.openwrt.pool.ntp.org' \ + --value '1.openwrt.pool.ntp.org' \ + --value '2.openwrt.pool.ntp.org' \ + --value '3.openwrt.pool.ntp.org' + + +SEE ALSO +-------- +- https://openwrt.org/docs/guide-user/base-system/uci + + +AUTHORS +------- +Dennis Camera + + +COPYING +------- +Copyright \(C) 2020 Dennis Camera. You can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. diff --git a/cdist/conf/type/__uci/manifest b/cdist/conf/type/__uci/manifest new file mode 100755 index 00000000..26920011 --- /dev/null +++ b/cdist/conf/type/__uci/manifest @@ -0,0 +1,51 @@ +#!/bin/sh -e +# +# 2020 Dennis Camera (dennis.camera@ssrq-sds-fds.ch) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# + +os=$(cat "${__global:?}/explorer/os") + +state_should=$(cat "${__object:?}/parameter/state") + +case ${os} +in + (openwrt) + # okay + ;; + (*) + printf "Your operating system (%s) is currently not supported by this type (%s)\n" "${os}" "${__type##*/}" >&2 + printf "Please contribute an implementation for it if you can.\n" >&2 + exit 1 + ;; +esac + +case ${state_should} +in + (present) + test -s "${__object:?}/parameter/value" || { + echo 'The parameter --value is required.' >&2 + exit 1 + } + ;; + (absent) + ;; + (*) + printf 'Invalid --state: %s\n' "${state_should}" >&2 + exit 1 + ;; +esac diff --git a/cdist/conf/type/__uci/nonparallel b/cdist/conf/type/__uci/nonparallel new file mode 100644 index 00000000..e69de29b diff --git a/cdist/conf/type/__uci/parameter/default/state b/cdist/conf/type/__uci/parameter/default/state new file mode 100644 index 00000000..e7f6134f --- /dev/null +++ b/cdist/conf/type/__uci/parameter/default/state @@ -0,0 +1 @@ +present diff --git a/cdist/conf/type/__uci/parameter/optional b/cdist/conf/type/__uci/parameter/optional new file mode 100644 index 00000000..d9080e3a --- /dev/null +++ b/cdist/conf/type/__uci/parameter/optional @@ -0,0 +1,2 @@ +state +type diff --git a/cdist/conf/type/__uci/parameter/optional_multiple b/cdist/conf/type/__uci/parameter/optional_multiple new file mode 100644 index 00000000..6d4e1507 --- /dev/null +++ b/cdist/conf/type/__uci/parameter/optional_multiple @@ -0,0 +1 @@ +value diff --git a/cdist/conf/type/__uci_section/explorer/match b/cdist/conf/type/__uci_section/explorer/match new file mode 100644 index 00000000..0768e404 --- /dev/null +++ b/cdist/conf/type/__uci_section/explorer/match @@ -0,0 +1,103 @@ +#!/bin/sh -e +# +# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# +# This explorer determines the "prefix" of the --type section matching --match +# if set, or __object_id otherwise. + +RS=$(printf '\036') +NL=$(printf '\n '); NL=${NL% } + +squote_values() { + sed -e '/=".*"$/{s/="/='\''/;s/"$/'\''/}' \ + -e "/='.*'$/"'!{s/=/='\''/;s/$/'\''/}' +} +count_lines() ( + IFS=${NL?} + # shellcheck disable=SC2048,SC2086 + set -f -- $*; echo $# +) + +echo "${__object_id:?}" | grep -q -e '^[^.]\{1,\}\.[^.]\{1,\}$' || { + echo 'Section identifiers are a package and section name separated by a "." (period).' >&2 + exit 1 +} + +test -s "${__object:?}/parameter/match" || { + # If no --match is given, we take the __object_id as the section identifier. + echo "${__object_id:?}" + exit 0 +} +test -s "${__object:?}/parameter/type" || { + echo 'Parameters --match and --type must be used together.' >&2 + exit 1 +} + +sect_type_param=$(cat "${__object:?}/parameter/type") +expr "${sect_type_param}" : '[^.]\{1,\}\.[^.]\{1,\}$' >/dev/null 2>&1 || { + echo 'Section types are a package name and section type separated by a "." (period).' >&2 + exit 1 +} +package_filter=${sect_type_param%%.*} +section_filter=${sect_type_param#*.} + +# Find by --match +# NOTE: Apart from section types all values are printed in single quotes by uci show. +match=$(head -n 1 "${__object:?}/parameter/match" | squote_values) + +if uci -s -N get "${__object_id:?}" >/dev/null 2>&1 +then + # Named section exists: ensure if --match applies to it + # if the "matched" option does not exist (e.g. empty section) we use the + # section unconditionally. + if match_value_is=$(uci -s -N get "${__object_id:?}.${match%%=*}" 2>/dev/null) + then + match_value_should=$(expr "${match}" : ".*='\\(.*\\)'$") + + test "${match_value_is}" = "${match_value_should}" || { + printf 'Named section "%s" does not match --match "%s"\n' \ + "${__object_id:?}" "${match}" >&2 + exit 1 + } + fi + + echo "${__object_id:?}" + exit 0 +fi + +# No correctly named section exists already: find one to which --match applies +regex="^${package_filter}\\.@${section_filter}\\[[0-9]\\{1,\\}\\]\\.${match%%=*}=" + +matched_sections=$( + uci -s -N -d "${RS}" show "${package_filter}" 2>/dev/null \ + | grep -e "${regex}" \ + | while read -r _line + do + if test "${_line#*=}" = "${match#*=}" + then + echo "${_line}" + fi + done \ + | sed -e 's/\.[^.]*=.*$//') + +test "$(count_lines "${matched_sections}")" -le 1 || { + printf 'Found multiple matching sections:\n%s\n' "${matched_sections}" >&2 + exit 1 +} + +echo "${matched_sections}" diff --git a/cdist/conf/type/__uci_section/explorer/options b/cdist/conf/type/__uci_section/explorer/options new file mode 100644 index 00000000..e1e60668 --- /dev/null +++ b/cdist/conf/type/__uci_section/explorer/options @@ -0,0 +1,48 @@ +#!/bin/sh -e +# +# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# +# This explorer retrieves the current options of the configuration section. + +RS=$(printf '\036') + +section=$("${__type_explorer:?}/match") +test -n "${section}" || exit 0 + +uci -s -N -d "${RS}" show "${section}" 2>/dev/null \ +| awk -v VSEP="${RS}" ' + { + # Strip off the config and section parts + is_opt = sub(/^([^.]*\.){2}/, "") + + if (!is_opt) { + # this line represents the section -> skip + next + } + + if (index($0, VSEP)) { + # Put values each on a line, like --option and --list parameters + opt = substr($0, 1, index($0, "=") - 1) + split(substr($0, length(opt) + 2), values, VSEP) + for (i in values) { + printf "%s=%s\n", opt, values[i] + } + } else { + print + } + }' diff --git a/cdist/conf/type/__uci_section/explorer/type b/cdist/conf/type/__uci_section/explorer/type new file mode 100644 index 00000000..1675c2e0 --- /dev/null +++ b/cdist/conf/type/__uci_section/explorer/type @@ -0,0 +1,25 @@ +#!/bin/sh -e +# +# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# +# This explorer retrieves the current section type. + +section=$("${__type_explorer:?}/match") +test -n "${section}" || exit 0 + +uci -s -N get "${section}" 2>/dev/null || true diff --git a/cdist/conf/type/__uci_section/files/functions.sh b/cdist/conf/type/__uci_section/files/functions.sh new file mode 100644 index 00000000..60cb9148 --- /dev/null +++ b/cdist/conf/type/__uci_section/files/functions.sh @@ -0,0 +1,59 @@ +# -*- mode: sh; indent-tabs-mode: t -*- + +NL=$(printf '\n '); NL=${NL% } + +grep_line() { + { shift; printf '%s\n' "$@"; } | grep -qxF "$1" +} + +print_errors() { + awk -v prefix="${1:-Found errors:}" -v suffix="${2-}" ' + BEGIN { + if (getline) { + print prefix + print + rc = 1 + } + } + { print } + END { + if (rc && suffix) print suffix + exit rc + }' >&2 +} + +quote() { + for _arg + do + shift + if test -n "$(printf %s "${_arg}" | tr -d -c '\t\n \042-\047\050-\052\073-\077\133\\`|~' | tr -c '' '.')" + then + # needs quoting + set -- "$@" "$(printf "'%s'" "$(printf %s "${_arg}" | sed -e "s/'/'\\\\''/g")")" + else + set -- "$@" "${_arg}" + fi + done + unset _arg + printf '%s' "$*" +} + +uci_cmd() { + # Usage: uci_cmd [UCI ARGUMENTS]... + mkdir -p "${__object:?}/files" + printf '%s\n' "$(quote "$@")" >>"${__object:?}/files/uci_batch.txt" +} + +uci_validate_name() { + # like util.c uci_validate_name() + test -n "$*" && test -z "$(printf %s "$*" | tr -d '[:alnum:]_' | tr -c '' .)" +} + +unquote_lines() { + sed -e '/^".*"$/{s/^"//;s/"$//}' \ + -e '/'"^'.*'"'$/{s/'"^'"'//;s/'"'$"'//}' +} + +validate_options() { + grep -shv -e '^[[:alnum:]_]\{1,\}=' "$@" +} diff --git a/cdist/conf/type/__uci_section/files/option_state.awk b/cdist/conf/type/__uci_section/files/option_state.awk new file mode 100644 index 00000000..97cd94fb --- /dev/null +++ b/cdist/conf/type/__uci_section/files/option_state.awk @@ -0,0 +1,91 @@ +# -*- mode: awk; indent-tabs-mode:t -*- +# Usage: awk -f option_state.awk option_type option_name +# e.g. awk -f option_state.awk option title +# awk -f option_state.awk list entry + +function unquote(s) { + # simplified dequoting of single quoted strings + if (s ~ /^'.*'$/) { + s = substr(s, 2, length(s) - 2) + sub(/'\\''/, "'", s) + } + return s +} + +function valueof(line) { + if (line !~ /^[[:alpha:]_]+=/) return 0 + return unquote(substr(line, index(line, "=") + 1)) +} + +BEGIN { + __object = ENVIRON["__object"] + if (!__object) exit 1 + + opttype = ARGV[1] + optname = ARGV[2] + + if (opttype !~ /^(option|list)/ || !optname) { + print "invalid" + exit (e=1) + } + + ARGV[1] = __object "/parameter/" opttype + ARGV[2] = __object "/explorer/options" + + state = "present" +} + +NR == FNR { + # memoize "should" state + if (index($0, optname "=") == 1) { + should[++should_count] = valueof($0) + } + + # go to next line (important!) + next +} + +{ + # compare "is" state + if (index($0, optname "=") != 1) + next + ++is_count + + v = valueof($0) + + if (v == should[is_count]) { + # looks good, but can't say definitely just from this line + } else if (is_count > should_count) { + # there are more "is" records than "should" -> definitely different + state = "different" + exit + } else { + # see if we can find the "is" value somewhere in "should" + for (i in should) { + if (v == should[i]) { + # value found -> could be rearranged + # FIXME: Duplicate values are not properly handled here. Do they matter? + state = "rearranged" + next + } + } + + # "is" value could not be found in "should" -> definitely different + state = "different" + exit + } +} + +END { + if (e) exit + + if (!is_count) { + # no "is" values -> absent + state = "absent" + } else if (is_count < should_count) { + # "is" was shorter than "should" -> different + state = "different" + } + + print state +} diff --git a/cdist/conf/type/__uci_section/files/uci_apply.sh b/cdist/conf/type/__uci_section/files/uci_apply.sh new file mode 120000 index 00000000..4209151f --- /dev/null +++ b/cdist/conf/type/__uci_section/files/uci_apply.sh @@ -0,0 +1 @@ +../../__uci/files/uci_apply.sh \ No newline at end of file diff --git a/cdist/conf/type/__uci_section/gencode-remote b/cdist/conf/type/__uci_section/gencode-remote new file mode 100755 index 00000000..285659b0 --- /dev/null +++ b/cdist/conf/type/__uci_section/gencode-remote @@ -0,0 +1,174 @@ +#!/bin/sh -e +# +# 2020 Dennis Camera (dennis.camera@ssrq-sds-fds.ch) +# +# This file is part of cdist. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with cdist. If not, see . +# + +# shellcheck source=files/functions.sh +. "${__type:?}/files/functions.sh" + + +section=$(cat "${__object:?}/explorer/match") + +state_is=$(test -s "${__object:?}/explorer/type" && echo present || echo absent) +state_should=$(cat "${__object:?}/parameter/state") + +case $state_should +in + (present) + test -f "${__object:?}/parameter/type" || { + echo 'Parameter --type is required.' >&2 + exit 1 + } + + type_is=$(cat "${__object:?}/explorer/type") + type_should=$(cat "${__object:?}/parameter/type") + + if test -n "${type_is}" + then + sect_type=${type_is} + else + sect_type=${type_should##*.} + fi + + if test -z "${section}" + then + # No section exists and --match was used. + # So we generate a new section identifier from $__object_id. + case ${__object_id:?} + in + (*.*) section=${__object_id:?} ;; + (*) section="${type_should%%.*}.${__object_id:?}" ;; + esac + fi + + # Collect option names + if test -f "${__object:?}/parameter/list" + then + listnames_should=$( + sed -e 's/=.*$//' "${__object:?}/parameter/list" | sort -u) + fi + + if test -f "${__object:?}/parameter/option" + then + optnames_should=$( + sed -e 's/=.*$//' "${__object:?}/parameter/option" | sort -u) + fi + + # Make sure the section itself is present + if test "${state_is}" = absent \ + || test "${type_is}" != "${type_should#*.}" + then + printf 'set %s\n' "${section}" >>"${__messages_out:?}" + # shellcheck disable=SC2140 + uci_cmd set "${section}"="${sect_type}" + fi + + # Delete options/lists not in "should" + sed -e 's/=.*$//' "${__object:?}/explorer/options" \ + | while read -r _optname + do + grep_line "${_optname}" "${listnames_should}" "${optnames_should}" || { + printf 'delete %s\n' "${section}.${_optname}" >>"${__messages_out:?}" + uci_cmd delete "${section}.${_optname}" + } &2 + exit 1 + } + + # Set "should" options + echo "${optnames_should}" \ + | grep -e . \ + | while read -r _optname + do + _opt_state=$(awk -f "${__type:?}/files/option_state.awk" option "${_optname}") \ + || opt_proc_error "${_optname}" + case ${_opt_state} + in + (invalid) + opt_proc_error "${_optname}" + ;; + (present) + ;; + (*) + printf 'set %s\n' "${section}.${_optname}" >>"${__messages_out:?}" + + # shellcheck disable=SC2140 + uci_cmd set "${section}.${_optname}"="$( + grep -e "^${_optname}=" "${__object:?}/parameter/option" \ + | sed -e 's/^.*=//' \ + | unquote_lines \ + | head -n 1)" + ;; + esac + done + + echo "${listnames_should}" \ + | grep -e . \ + | while read -r _optname + do + _list_state=$(awk -f "${__type:?}/files/option_state.awk" list "${_optname}") \ + || opt_proc_error "${_optname}" + case ${_list_state} + in + (invalid) + opt_proc_error "${_optname}" + ;; + (present) + ;; + (*) + printf 'set_list %s\n' "${section}.${_optname}" >>"${__messages_out:?}" + + if test "${_list_state}" != absent + then + uci_cmd delete "${section}.${_optname}" + fi + + grep "^${_optname}=" "${__object:?}/parameter/list" \ + | sed -e 's/^.*=//' \ + | unquote_lines \ + | while read -r _value + do + # shellcheck disable=SC2140 + uci_cmd add_list "${section}.${_optname}"="${_value}" + done + ;; + esac + done + ;; + (absent) + if test "${state_is}" = absent + then + # if explorer found no section there is nothing to delete + exit 0 + fi + + printf 'delete %s\n' "${section}" >>"${__messages_out:?}" + uci_cmd delete "${section}" + ;; +esac + +if test -s "${__object:?}/files/uci_batch.txt" +then + cat "${__type:?}/files/uci_apply.sh" + printf "uci_apply <<'EOF'\n" + cat "${__object:?}/files/uci_batch.txt" + printf '\nEOF\n' +fi diff --git a/cdist/conf/type/__uci_section/man.rst b/cdist/conf/type/__uci_section/man.rst new file mode 100644 index 00000000..a0ab78e8 --- /dev/null +++ b/cdist/conf/type/__uci_section/man.rst @@ -0,0 +1,119 @@ +cdist-type__uci_section(7) +========================== + +NAME +---- +cdist-type__uci_section - Manage configuration sections in UCI + + +DESCRIPTION +----------- +This cdist type can be used to replace whole configuration sections in OpenWrt's +Unified Configuration Interface (UCI) system. +It can be thought of as syntactic sugar for :strong:`cdist-type__uci`\ (7), +as this type will generate the required `__uci` objects to make the section +contain exactly the options specified using ``--option``. + +Since many default UCI sections are unnamed, this type allows to find the +matching section by one of its options using the ``--match`` parameter. + +**NOTE:** Options already present on the target and not listed in ``--option`` +or ``--list`` will be deleted. + + +REQUIRED PARAMETERS +------------------- +None. + + +OPTIONAL PARAMETERS +------------------- +list + An option that is part of a list and should be present in the section (as + part of a list). Lists with multiple options can be expressed by using the + same ``