diff --git a/Makefile b/Makefile index f89ac1e7..3712511c 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,7 @@ version: } # Manpages #3: generic part -man: version $(MANTYPES) $(DOCSREF) +man: version configskel $(MANTYPES) $(DOCSREF) $(DOCSTYPESREF) $(SPHINXM) html: version configskel $(MANTYPES) $(DOCSREF) $(DOCSTYPESREF) @@ -104,7 +104,7 @@ DOTMANTYPES=$(subst /man.rst,.rst,$(DOTMANTYPEPREFIX)) $(DOTMAN7DSTDIR)/cdist-type%.rst: $(DOTTYPEDIR)/%/man.rst ln -sf "$^" $@ -dotman: version $(DOTMANTYPES) +dotman: version configskel $(DOTMANTYPES) $(DOCSREF) $(DOCSTYPESREF) $(SPHINXM) ################################################################################ diff --git a/README b/README deleted file mode 100644 index caf2dac8..00000000 --- a/README +++ /dev/null @@ -1,7 +0,0 @@ -cdist ------ - -cdist is a usable configuration management system. - -For the web documentation have a look at https://www.cdi.st/ -or at docs/src for reStructuredText manual. diff --git a/README.md b/README.md new file mode 100644 index 00000000..de6901c7 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# cdist + +**cdist** is a usable configuration management system. + +It adheres to the [**KISS principle**](https://en.wikipedia.org/wiki/KISS_principle) +and is being used in small up to enterprise grade environments. + +For more information have a look at [**homepage**](https://cdi.st) +or at **``docs/src``** for manual in **reStructuredText** format. + +## Contributing + +Merge/Pull requests can be made in both +[upstream **GitLab**](https://code.ungleich.ch/ungleich-public/cdist/merge_requests) +(managed by [**ungleich**](https://ungleich.ch)) +and [**GitHub** project](https://github.com/ungleich/cdist/pulls). + +Issues can be made and other project management activites happen +[**only in GitLab**](https://code.ungleich.ch/ungleich-public/cdist) +(needs [**ungleich** account](https://account.ungleich.ch)). + +For community-maintained types there is +[**cdist-contrib** project](https://code.ungleich.ch/ungleich-public/cdist-contrib). + +## Participating + +IRC: ``#cdist`` @ freenode + +Matrix: ``#cdist:ungleich.ch`` + +Mattermost: https://chat.ungleich.ch/ungleich/channels/cdist diff --git a/bin/build-helper b/bin/build-helper index ed41e438..d4d603ed 100755 --- a/bin/build-helper +++ b/bin/build-helper @@ -371,7 +371,6 @@ eof Manual steps post release: - cdist-web - send generated mailinglist.tmp mail - - twitter eof ;; diff --git a/cdist/__init__.py b/cdist/__init__.py index c673b3ba..be573170 100644 --- a/cdist/__init__.py +++ b/cdist/__init__.py @@ -26,6 +26,7 @@ import hashlib import cdist.log import cdist.version + VERSION = cdist.version.VERSION BANNER = """ @@ -48,6 +49,9 @@ REMOTE_EXEC = "ssh -o User=root" REMOTE_CMDS_CLEANUP_PATTERN = "ssh -o User=root -O exit -S {}" +MIN_SUPPORTED_PYTHON_VERSION = '3.5' + + class Error(Exception): """Base exception class for this project""" pass diff --git a/cdist/argparse.py b/cdist/argparse.py index 611c484a..1d16bb25 100644 --- a/cdist/argparse.py +++ b/cdist/argparse.py @@ -5,6 +5,7 @@ import logging import collections import functools import cdist.configuration +import cdist.log import cdist.preos import cdist.info @@ -125,6 +126,14 @@ def get_parsers(): 'value.'), action='count', default=None) + parser['colored_output'] = argparse.ArgumentParser(add_help=False) + parser['colored_output'].add_argument( + '--colors', metavar='WHEN', + help="Colorize cdist's output based on log level; " + "WHEN is 'always', 'never', or 'auto'.", + action='store', dest='colored_output', required=False, + choices=cdist.configuration.ColoredOutputOption.CHOICES) + parser['beta'] = argparse.ArgumentParser(add_help=False) parser['beta'].add_argument( '-b', '--beta', @@ -197,6 +206,13 @@ def get_parsers(): 'supported. Without argument CPU count is used by default. '), action='store', dest='jobs', const=multiprocessing.cpu_count()) + parser['config_main'].add_argument( + '--log-server', + action='store_true', + help=('Start a log server for sub processes to use. ' + 'This is mainly useful when running cdist nested ' + 'from a code-local script. Log server is alwasy ' + 'implicitly started for \'install\' command.')) parser['config_main'].add_argument( '-n', '--dry-run', help='Do not execute code.', action='store_true') @@ -257,8 +273,7 @@ def get_parsers(): '-f', '--file', help=('Read specified file for a list of additional hosts to ' 'operate on or if \'-\' is given, read stdin (one host per ' - 'line). If no host or host file is specified then, by ' - 'default, read hosts from stdin.'), + 'line).'), dest='hostfile', required=False) parser['config_args'].add_argument( '-p', '--parallel', nargs='?', metavar='HOST_MAX', @@ -283,6 +298,7 @@ def get_parsers(): 'host', nargs='*', help='Host(s) to operate on.') parser['config'] = parser['sub'].add_parser( 'config', parents=[parser['loglevel'], parser['beta'], + parser['colored_output'], parser['common'], parser['config_main'], parser['inventory_common'], @@ -301,6 +317,7 @@ def get_parsers(): parser['add-host'] = parser['invsub'].add_parser( 'add-host', parents=[parser['loglevel'], parser['beta'], + parser['colored_output'], parser['common'], parser['inventory_common']]) parser['add-host'].add_argument( @@ -308,13 +325,12 @@ def get_parsers(): parser['add-host'].add_argument( '-f', '--file', help=('Read additional hosts to add from specified file ' - 'or from stdin if \'-\' (each host on separate line). ' - 'If no host or host file is specified then, by default, ' - 'read from stdin.'), + 'or from stdin if \'-\' (each host on separate line). '), dest='hostfile', required=False) parser['add-tag'] = parser['invsub'].add_parser( 'add-tag', parents=[parser['loglevel'], parser['beta'], + parser['colored_output'], parser['common'], parser['inventory_common']]) parser['add-tag'].add_argument( @@ -323,20 +339,12 @@ def get_parsers(): parser['add-tag'].add_argument( '-f', '--file', help=('Read additional hosts to add tags from specified file ' - 'or from stdin if \'-\' (each host on separate line). ' - 'If no host or host file is specified then, by default, ' - 'read from stdin. If no tags/tagfile nor hosts/hostfile' - ' are specified then tags are read from stdin and are' - ' added to all hosts.'), + 'or from stdin if \'-\' (each host on separate line). '), dest='hostfile', required=False) parser['add-tag'].add_argument( '-T', '--tag-file', help=('Read additional tags to add from specified file ' - 'or from stdin if \'-\' (each tag on separate line). ' - 'If no tag or tag file is specified then, by default, ' - 'read from stdin. If no tags/tagfile nor hosts/hostfile' - ' are specified then tags are read from stdin and are' - ' added to all hosts.'), + 'or from stdin if \'-\' (each tag on separate line). '), dest='tagfile', required=False) parser['add-tag'].add_argument( '-t', '--taglist', @@ -346,6 +354,7 @@ def get_parsers(): parser['del-host'] = parser['invsub'].add_parser( 'del-host', parents=[parser['loglevel'], parser['beta'], + parser['colored_output'], parser['common'], parser['inventory_common']]) parser['del-host'].add_argument( @@ -356,13 +365,12 @@ def get_parsers(): parser['del-host'].add_argument( '-f', '--file', help=('Read additional hosts to delete from specified file ' - 'or from stdin if \'-\' (each host on separate line). ' - 'If no host or host file is specified then, by default, ' - 'read from stdin.'), + 'or from stdin if \'-\' (each host on separate line). '), dest='hostfile', required=False) parser['del-tag'] = parser['invsub'].add_parser( 'del-tag', parents=[parser['loglevel'], parser['beta'], + parser['colored_output'], parser['common'], parser['inventory_common']]) parser['del-tag'].add_argument( @@ -375,20 +383,13 @@ def get_parsers(): parser['del-tag'].add_argument( '-f', '--file', help=('Read additional hosts to delete tags for from specified ' - 'file or from stdin if \'-\' (each host on separate line). ' - 'If no host or host file is specified then, by default, ' - 'read from stdin. If no tags/tagfile nor hosts/hostfile' - ' are specified then tags are read from stdin and are' - ' deleted from all hosts.'), + 'file or from stdin if \'-\' (each host on separate ' + 'line). '), dest='hostfile', required=False) parser['del-tag'].add_argument( '-T', '--tag-file', help=('Read additional tags from specified file ' - 'or from stdin if \'-\' (each tag on separate line). ' - 'If no tag or tag file is specified then, by default, ' - 'read from stdin. If no tags/tagfile nor' - ' hosts/hostfile are specified then tags are read from' - ' stdin and are added to all hosts.'), + 'or from stdin if \'-\' (each tag on separate line). '), dest='tagfile', required=False) parser['del-tag'].add_argument( '-t', '--taglist', @@ -398,6 +399,7 @@ def get_parsers(): parser['list'] = parser['invsub'].add_parser( 'list', parents=[parser['loglevel'], parser['beta'], + parser['colored_output'], parser['common'], parser['inventory_common']]) parser['list'].add_argument( @@ -430,7 +432,7 @@ def get_parsers(): # Shell parser['shell'] = parser['sub'].add_parser( - 'shell', parents=[parser['loglevel']]) + 'shell', parents=[parser['loglevel'], parser['colored_output']]) parser['shell'].add_argument( '-s', '--shell', help=('Select shell to use, defaults to current shell. Used shell' @@ -478,7 +480,12 @@ def handle_loglevel(args): if hasattr(args, 'quiet') and args.quiet: args.verbose = _verbosity_level_off - logging.root.setLevel(_verbosity_level[args.verbose]) + logging.getLogger().setLevel(_verbosity_level[args.verbose]) + + +def handle_log_colors(args): + if cdist.configuration.ColoredOutputOption.translate(args.colored_output): + cdist.log.CdistFormatter.USE_COLORS = True def parse_and_configure(argv, singleton=True): @@ -492,6 +499,7 @@ def parse_and_configure(argv, singleton=True): raise cdist.Error(str(e)) # Loglevels are handled globally in here handle_loglevel(args) + handle_log_colors(args) log = logging.getLogger("cdist") diff --git a/cdist/conf/explorer/cpu_cores b/cdist/conf/explorer/cpu_cores index a52bddac..81e5294e 100755 --- a/cdist/conf/explorer/cpu_cores +++ b/cdist/conf/explorer/cpu_cores @@ -32,6 +32,11 @@ case "$os" in sysctl -n hw.ncpuonline ;; + "freebsd"|"netbsd") + PATH=$(getconf PATH) + sysctl -n hw.ncpu + ;; + *) if [ -r /proc/cpuinfo ]; then cores="$(grep "core id" /proc/cpuinfo | sort | uniq | wc -l)" diff --git a/cdist/conf/explorer/disks b/cdist/conf/explorer/disks index 24540601..56d62d10 100755 --- a/cdist/conf/explorer/disks +++ b/cdist/conf/explorer/disks @@ -30,9 +30,8 @@ case $uname_s in sysctl -n hw.disknames | grep -Eo '[lsw]d[0-9]+' ;; NetBSD) - PATH="${PATH}:/usr/local/sbin:/usr/sbin:/sbin" - sysctl -n hw.disknames \ - | awk 'BEGIN { RS = " " } /^[lsw]d[0-9]+/' + PATH=$(getconf PATH) + sysctl -n hw.disknames | awk -v RS=' ' '/^[lsw]d[0-9]+/' ;; Linux) # list of major device numbers toexclude: diff --git a/cdist/conf/explorer/machine_type b/cdist/conf/explorer/machine_type index bb21f69c..1c84f4d7 100755 --- a/cdist/conf/explorer/machine_type +++ b/cdist/conf/explorer/machine_type @@ -2,6 +2,7 @@ # # 2014 Daniel Heule (hda at sfs.biz) # 2014 Thomas Oettli (otho at sfs.biz) +# 2020 Evilham (contact at evilham.com) # # This file is part of cdist. # @@ -18,63 +19,91 @@ # You should have received a copy of the GNU General Public License # along with cdist. If not, see . # -# -# FIXME: other system types (not linux ...) +os=$("$__explorer/os") -if [ -d "/proc/vz" ] && [ ! -d "/proc/bc" ]; then - echo openvz - exit -fi - -if [ -e "/proc/1/environ" ] && - tr '\000' '\n' < "/proc/1/environ" | grep -Eiq '^container='; then - echo lxc - exit -fi - -if [ -r /proc/cpuinfo ]; then - # this should only exist on virtual guest machines, - # tested on vmware, xen, kvm - if grep -q "hypervisor" /proc/cpuinfo; then - # this file is aviable in xen guest systems - if [ -r /sys/hypervisor/type ]; then - if grep -q -i "xen" /sys/hypervisor/type; then - echo virtual_by_xen - exit +vendor_string_to_machine_type() { + for vendor in vmware bochs kvm qemu virtualbox bhyve; do + if echo "${1}" | grep -q -i "${vendor}"; then + if [ "${vendor}" = "bochs" ] || [ "${vendor}" = "qemu" ]; then + vendor="kvm" fi - else - if [ -r /sys/class/dmi/id/product_name ]; then - if grep -q -i 'vmware' /sys/class/dmi/id/product_name; then - echo "virtual_by_vmware" - exit - elif grep -q -i 'bochs' /sys/class/dmi/id/product_name; then - echo "virtual_by_kvm" - exit - elif grep -q -i 'virtualbox' /sys/class/dmi/id/product_name; then - echo "virtual_by_virtualbox" - exit - fi - fi - - if [ -r /sys/class/dmi/id/sys_vendor ]; then - if grep -q -i 'qemu' /sys/class/dmi/id/sys_vendor; then - echo "virtual_by_kvm" - exit - fi - fi - - if [ -r /sys/class/dmi/id/chassis_vendor ]; then - if grep -q -i 'qemu' /sys/class/dmi/id/chassis_vendor; then - echo "virtual_by_kvm" - exit - fi - fi + echo "virtual_by_${vendor}" + exit fi - echo "virtual_by_unknown" - else - echo "physical" - fi -else - echo "unknown" -fi + done +} + +case "$os" in + "freebsd") + # FreeBSD does not have /proc/cpuinfo even when procfs is used. + # Instead there is a sysctl kern.vm_guest. + # Which is 'none' if physical, else the virtualisation. + vm_guest="$(sysctl -n kern.vm_guest 2>/dev/null || true)" + if [ -n "${vm_guest}" ]; then + if [ "${vm_guest}" = "none" ]; then + echo "physical" + exit + fi + echo "virtual_by_${vm_guest}" + exit + fi + ;; + + "openbsd") + # OpenBSD can also use the sysctl's: hw.vendor or hw.product. + # Note we can be reasonably sure about a machine being virtualised + # as long as we can identify the virtualisation technology. + # But not so much about it being physical... + # Patches are welcome / reach out if you have better ideas. + for sysctl in hw.vendor hw.product; do + # This exits if we can make a reasonable judgement + vendor_string_to_machine_type "$(sysctl -n "${sysctl}")" + done + ;; + + *) + # Defaulting to linux for compatibility with previous cdist behaviour + + if [ -d "/proc/vz" ] && [ ! -d "/proc/bc" ]; then + echo openvz + exit + fi + + if [ -e "/proc/1/environ" ] && + tr '\000' '\n' < "/proc/1/environ" | grep -Eiq '^container='; then + echo lxc + exit + fi + + if [ -r /proc/cpuinfo ]; then + # this should only exist on virtual guest machines, + # tested on vmware, xen, kvm, bhyve + if grep -q "hypervisor" /proc/cpuinfo; then + # this file is aviable in xen guest systems + if [ -r /sys/hypervisor/type ]; then + if grep -q -i "xen" /sys/hypervisor/type; then + echo virtual_by_xen + exit + fi + else + for vendor_file in /sys/class/dmi/id/product_name \ + /sys/class/dmi/id/sys_vendor \ + /sys/class/dmi/id/chasis_vendor; do + if [ -r ${vendor_file} ]; then + # This exits if we can make a reasonable judgement + vendor_string_to_machine_type "$(cat "${vendor_file}")" + fi + done + fi + echo "virtual_by_unknown" + exit + else + echo "physical" + exit + fi + fi + ;; +esac + +echo "unknown" diff --git a/cdist/conf/explorer/memory b/cdist/conf/explorer/memory index 4e3efff8..5ea15ada 100755 --- a/cdist/conf/explorer/memory +++ b/cdist/conf/explorer/memory @@ -29,7 +29,8 @@ case "$os" in echo "$(sysctl -n hw.memsize)/1024" | bc ;; - "openbsd") + *"bsd") + PATH=$(getconf PATH) echo "$(sysctl -n hw.physmem) / 1048576" | bc ;; diff --git a/cdist/conf/explorer/os b/cdist/conf/explorer/os index 563fa4cf..46d87f3e 100755 --- a/cdist/conf/explorer/os +++ b/cdist/conf/explorer/os @@ -143,6 +143,13 @@ case "$uname_s" in esac if [ -f /etc/os-release ]; then + # after sles15, suse don't provide an /etc/SuSE-release anymore, but there is almost no difference between sles and opensuse leap, so call it suse + # shellcheck disable=SC1091 + if (. /etc/os-release && echo "${ID_LIKE}" | grep -q '\(^\|\ \)suse\($\|\ \)') + then + echo suse + exit 0 + fi # already lowercase, according to: # https://www.freedesktop.org/software/systemd/man/os-release.html awk -F= '/^ID=/ { if ($2 ~ /^'"'"'(.*)'"'"'$/ || $2 ~ /^"(.*)"$/) { print substr($2, 2, length($2) - 2) } else { print $2 } }' /etc/os-release diff --git a/cdist/conf/explorer/os_version b/cdist/conf/explorer/os_version index 1d54ea60..a7b1d3bc 100755 --- a/cdist/conf/explorer/os_version +++ b/cdist/conf/explorer/os_version @@ -31,7 +31,32 @@ case "$("$__explorer/os")" in cat /etc/arch-release ;; debian) - cat /etc/debian_version + debian_version=$(cat /etc/debian_version) + case $debian_version + in + testing/unstable) + # previous to Debian 4.0 testing/unstable was used + # cf. https://metadata.ftp-master.debian.org/changelogs/main/b/base-files/base-files_11_changelog + echo 3.99 + ;; + */sid) + # sid versions don't have a number, so we decode by codename: + case $(expr "$debian_version" : '\([a-z]\{1,\}\)/') + in + bullseye) echo 10.99 ;; + buster) echo 9.99 ;; + stretch) echo 8.99 ;; + jessie) echo 7.99 ;; + wheezy) echo 6.99 ;; + squeeze) echo 5.99 ;; + lenny) echo 4.99 ;; + *) exit 1 + esac + ;; + *) + echo "$debian_version" + ;; + esac ;; devuan) cat /etc/devuan_version @@ -73,4 +98,4 @@ case "$("$__explorer/os")" in alpine) cat /etc/alpine-release ;; -esac \ No newline at end of file +esac diff --git a/cdist/conf/type/__clean_path/explorer/list b/cdist/conf/type/__clean_path/explorer/list index 07d38127..2bdc63a5 100755 --- a/cdist/conf/type/__clean_path/explorer/list +++ b/cdist/conf/type/__clean_path/explorer/list @@ -18,7 +18,12 @@ # along with cdist. If not, see . # -path="/$__object_id" +if [ -f "$__object/parameter/path" ] +then + path="$( cat "$__object/parameter/path" )" +else + path="/$__object_id" +fi [ ! -d "$path" ] && exit 0 diff --git a/cdist/conf/type/__clean_path/gencode-remote b/cdist/conf/type/__clean_path/gencode-remote index 998a70d8..2899c4a5 100755 --- a/cdist/conf/type/__clean_path/gencode-remote +++ b/cdist/conf/type/__clean_path/gencode-remote @@ -20,7 +20,12 @@ [ ! -s "$__object/explorer/list" ] && exit 0 -path="/$__object_id" +if [ -f "$__object/parameter/path" ] +then + path="$( cat "$__object/parameter/path" )" +else + path="/$__object_id" +fi pattern="$( cat "$__object/parameter/pattern" )" diff --git a/cdist/conf/type/__clean_path/man.rst b/cdist/conf/type/__clean_path/man.rst index 826f4589..31d90701 100644 --- a/cdist/conf/type/__clean_path/man.rst +++ b/cdist/conf/type/__clean_path/man.rst @@ -10,7 +10,7 @@ DESCRIPTION ----------- Remove files and directories which match the pattern. -Provided path (as __object_id) must be a directory. +Provided path must be a directory. Patterns are passed to ``find``'s ``-regex`` - see ``find(1)`` for more details. @@ -29,6 +29,9 @@ pattern OPTIONAL PARAMETERS ------------------- +path + Path which will be cleaned. Defaults to ``$__object_id``. + exclude Pattern of files which are excluded from removal. @@ -46,6 +49,11 @@ EXAMPLES --exclude '.+\(charset\.conf\|security\.conf\)' \ --onchange 'service apache2 restart' + __clean_path apache2-conf-enabled \ + --path /etc/apache2/conf-enabled \ + --pattern '.+' \ + --exclude '.+\(charset\.conf\|security\.conf\)' \ + --onchange 'service apache2 restart' AUTHORS ------- diff --git a/cdist/conf/type/__clean_path/parameter/optional b/cdist/conf/type/__clean_path/parameter/optional index 6f313474..3b97f71c 100644 --- a/cdist/conf/type/__clean_path/parameter/optional +++ b/cdist/conf/type/__clean_path/parameter/optional @@ -1,2 +1,3 @@ exclude onchange +path diff --git a/cdist/conf/type/__cron/man.rst b/cdist/conf/type/__cron/man.rst index d0694738..e39bfb5c 100644 --- a/cdist/conf/type/__cron/man.rst +++ b/cdist/conf/type/__cron/man.rst @@ -21,6 +21,11 @@ command OPTIONAL PARAMETERS ------------------- +**NOTE**: All time-related parameters (``--minute``, ``--hour``, ``--day_of_month`` +``--month`` and ``--day_of_week``) defaults to ``*``, which means to execute it +**always**. If you set ``--hour 0`` to execute the cronjob only at midnight, it +will execute **every** minute in the first hour of the morning all days. + state Either present or absent. Defaults to present. minute diff --git a/cdist/conf/type/__directory/explorer/stat b/cdist/conf/type/__directory/explorer/stat index 105d894f..f817cb02 100755 --- a/cdist/conf/type/__directory/explorer/stat +++ b/cdist/conf/type/__directory/explorer/stat @@ -30,10 +30,10 @@ fallback() { gid=$(echo "$ls_line" | awk '{ print $4 }') owner=$(awk -F: -v uid="$uid" '$3 == uid { print $1; f=1 } END { if (!f) print "UNKNOWN" }' /etc/passwd) - group=$(awk -F: -v uid="$uid" '$3 == uid { print $1; f=1 } END { if (!f) print "UNKNOWN" }' /etc/group) + group=$(awk -F: -v gid="$gid" '$3 == gid { print $1; f=1 } END { if (!f) print "UNKNOWN" }' /etc/group) mode_text=$(echo "$ls_line" | awk '{ print $1 }') - mode=$(echo "$mode_text" | awk '{ k=0; for (i=0; i<=8; i++) k += ((substr($1, i+2, 1) ~ /[rwx]/) * 2^(8-i)); printf("%0o", k) }') + mode=$(echo "$mode_text" | awk '{for(i=8;i>=0;--i){c=substr($1,10-i,1);k+=((c~/[rwxst]/)*2^i);if(!(i%3))k+=(tolower(c)~/[lst]/)*2^(9+i/3)}printf("%04o",k)}') printf 'type: %s\nowner: %d %s\ngroup: %d %s\nmode: %s %s\n' \ "$("$__type_explorer/type")" \ @@ -45,56 +45,27 @@ fallback() { # nothing to work with, nothing we could do [ -e "$destination" ] || exit 0 -if ! command -v stat >/dev/null -then +command -v stat >/dev/null 2>&1 || { fallback exit -fi +} -case $("$__explorer/os") in - "freebsd"|"netbsd"|"openbsd"|"macosx") - stat -f "type: %HT +case $("$__explorer/os") +in + freebsd|netbsd|openbsd|macosx) + stat -f 'type: %HT owner: %Du %Su group: %Dg %Sg -mode: %Lp %Sp -" "$destination" | awk '/^type/ { print tolower($0); next } { print }' +mode: %Mp%03Lp %Sp +' "$destination" | awk '/^type/ { print tolower($0); next } { print }' ;; - solaris) - ls1="$( ls -ld "$destination" )" - ls2="$( ls -ldn "$destination" )" - - if [ -f "$__object/parameter/mode" ] - then mode_should="$( cat "$__object/parameter/mode" )" - fi - - # yes, it is ugly hack, but if you know better way... - if [ -z "$( find "$destination" -perm "$mode_should" )" ] - then octets=888 - else octets="$( echo "$mode_should" | sed 's/^0//' )" - fi - - case "$( echo "$ls1" | cut -c1-1 )" in - -) echo 'type: regular file' ;; - d) echo 'type: directory' ;; - esac - - echo "owner: $( echo "$ls2" \ - | awk '{print $3}' ) $( echo "$ls1" \ - | awk '{print $3}' )" - - echo "group: $( echo "$ls2" \ - | awk '{print $4}' ) $( echo "$ls1" \ - | awk '{print $4}' )" - - echo "mode: $octets $( echo "$ls1" | awk '{print $1}' )" - ;; *) # NOTE: Do not use --printf here as it is not supported by BusyBox stat. # NOTE: BusyBox's stat might not support the "-c" option, in which case # we fall through to the shell fallback. - stat -c "type: %F + stat -c 'type: %F owner: %u %U group: %g %G -mode: %a %A" "$destination" 2>/dev/null || fallback - ;; +mode: %04a %A' "$destination" 2>/dev/null || fallback + ;; esac diff --git a/cdist/conf/type/__directory/gencode-remote b/cdist/conf/type/__directory/gencode-remote index a1a32ea2..d9c00b56 100755 --- a/cdist/conf/type/__directory/gencode-remote +++ b/cdist/conf/type/__directory/gencode-remote @@ -97,9 +97,11 @@ case "$state_should" in value_should="$(cat "$__object/parameter/$attribute")" value_is="$(get_current_value "$attribute" "$value_should")" - # change 0xxx format to xxx format => same as stat returns + # format mode in four digits => same as stat returns if [ "$attribute" = mode ]; then - value_should="$(echo "$value_should" | sed 's/^0\(...\)/\1/')" + # Convert to four-digit octal number (printf interprets + # strings with leading 0s as octal!) + value_should=$(printf '%04o' "0${value_should}") fi if [ "$set_attributes" = 1 ] || [ "$value_should" != "$value_is" ]; then diff --git a/cdist/conf/type/__download/explorer/remote_cmd b/cdist/conf/type/__download/explorer/remote_cmd new file mode 100755 index 00000000..e3e35b45 --- /dev/null +++ b/cdist/conf/type/__download/explorer/remote_cmd @@ -0,0 +1,19 @@ +#!/bin/sh -e + +if [ -f "$__object/parameter/cmd-get" ] +then + cmd="$( cat "$__object/parameter/cmd-get" )" + +elif command -v curl > /dev/null +then + cmd="curl -L -o - '%s'" + +elif command -v fetch > /dev/null +then + cmd="fetch -o - '%s'" + +else + cmd="wget -O - '%s'" +fi + +echo "$cmd" diff --git a/cdist/conf/type/__download/explorer/state b/cdist/conf/type/__download/explorer/state new file mode 100755 index 00000000..00362545 --- /dev/null +++ b/cdist/conf/type/__download/explorer/state @@ -0,0 +1,72 @@ +#!/bin/sh -e + +dst="/$__object_id" + +if [ ! -f "$dst" ] +then + echo 'absent' + exit 0 +fi + +sum_should="$( cat "$__object/parameter/sum" )" + +if [ -f "$__object/parameter/cmd-sum" ] +then + # shellcheck disable=SC2059 + sum_is="$( eval "$( printf \ + "$( cat "$__object/parameter/cmd-sum" )" \ + "$dst" )" )" +else + os="$( "$__explorer/os" )" + + if echo "$sum_should" | grep -Eq '^[0-9]+\s[0-9]+$' + then + sum_is="$( cksum "$dst" | awk '{print $1" "$2}' )" + + elif echo "$sum_should" | grep -Eiq '^md5:[a-f0-9]{32}$' + then + case "$os" in + freebsd) + sum_is="md5:$( md5 -q "$dst" )" + ;; + *) + sum_is="md5:$( md5sum "$dst" | awk '{print $1}' )" + ;; + esac + + elif echo "$sum_should" | grep -Eiq '^sha1:[a-f0-9]{40}$' + then + case "$os" in + freebsd) + sum_is="sha1:$( sha1 -q "$dst" )" + ;; + *) + sum_is="sha1:$( sha1sum "$dst" | awk '{print $1}' )" + ;; + esac + + elif echo "$sum_should" | grep -Eiq '^sha256:[a-f0-9]{64}$' + then + case "$os" in + freebsd) + sum_is="sha256:$( sha256 -q "$dst" )" + ;; + *) + sum_is="sha256:$( sha256sum "$dst" | awk '{print $1}' )" + ;; + esac + fi +fi + +if [ -z "$sum_is" ] +then + echo 'no checksum from target' >&2 + exit 1 +fi + +if [ "$sum_is" = "$sum_should" ] +then + echo 'present' +else + echo 'mismatch' +fi diff --git a/cdist/conf/type/__download/gencode-local b/cdist/conf/type/__download/gencode-local new file mode 100755 index 00000000..571d2c3c --- /dev/null +++ b/cdist/conf/type/__download/gencode-local @@ -0,0 +1,58 @@ +#!/bin/sh -e + +download="$( cat "$__object/parameter/download" )" + +state_is="$( cat "$__object/explorer/state" )" + +if [ "$download" != 'local' ] || [ "$state_is" = 'present' ] +then + exit 0 +fi + +url="$( cat "$__object/parameter/url" )" + +tmp="$( mktemp )" + +dst="/$__object_id" + +if [ -f "$__object/parameter/cmd-get" ] +then + cmd="$( cat "$__object/parameter/cmd-get" )" + +elif command -v wget > /dev/null +then + cmd="wget -O - '%s'" + +elif command -v curl > /dev/null +then + cmd="curl -L -o - '%s'" + +elif command -v fetch > /dev/null +then + cmd="fetch -o - '%s'" + +else + echo 'no usable locally installed utility for downloading' >&2 + exit 1 +fi + +printf "$cmd > %s\n" \ + "$url" \ + "$tmp" + +if echo "$__target_host" | grep -Eq '^[0-9a-fA-F:]+$' +then + target_host="[$__target_host]" +else + target_host="$__target_host" +fi + +printf '%s %s %s:%s\n' \ + "$__remote_copy" \ + "$tmp" \ + "$target_host" \ + "$dst" + +echo "rm -f '$tmp'" + +echo 'downloaded' > "$__messages_out" diff --git a/cdist/conf/type/__download/gencode-remote b/cdist/conf/type/__download/gencode-remote new file mode 100755 index 00000000..029a0801 --- /dev/null +++ b/cdist/conf/type/__download/gencode-remote @@ -0,0 +1,25 @@ +#!/bin/sh -e + +download="$( cat "$__object/parameter/download" )" + +state_is="$( cat "$__object/explorer/state" )" + +if [ "$download" = 'remote' ] && [ "$state_is" != 'present' ] +then + cmd="$( cat "$__object/explorer/remote_cmd" )" + + url="$( cat "$__object/parameter/url" )" + + dst="/$__object_id" + + printf "$cmd > %s\n" \ + "$url" \ + "$dst" + + echo 'downloaded' > "$__messages_out" +fi + +if [ -f "$__object/parameter/onchange" ] && [ "$state_is" != "present" ] +then + cat "$__object/parameter/onchange" +fi diff --git a/cdist/conf/type/__download/man.rst b/cdist/conf/type/__download/man.rst new file mode 100644 index 00000000..eb3ac971 --- /dev/null +++ b/cdist/conf/type/__download/man.rst @@ -0,0 +1,86 @@ +cdist-type__download(7) +======================= + +NAME +---- +cdist-type__download - Download a file + + +DESCRIPTION +----------- +Destination (``$__object_id``) in target host must be persistent storage +in order to calculate checksum and decide if file must be (re-)downloaded. + +By default type will try to use ``wget``, ``curl`` or ``fetch``. +If download happens in target (see ``--download``) then type will +fallback to (and install) ``wget``. + +If download happens in local machine, then environment variables like +``{http,https,ftp}_proxy`` etc can be used on cdist execution +(``http_proxy=foo cdist config ...``). + + +REQUIRED PARAMETERS +------------------- +url + File's URL. + +sum + Checksum of file going to be downloaded. + By default output of ``cksum`` without filename is expected. + Other hash formats supported with prefixes: ``md5:``, ``sha1:`` and ``sha256:``. + + +OPTIONAL PARAMETERS +------------------- +download + If ``local`` (default), then download file to local storage and copy + it to target host. If ``remote``, then download happens in target. + +cmd-get + Command used for downloading. + Command must output to ``stdout``. + Parameter will be used for ``printf`` and must include only one + format specification ``%s`` which will become URL. + For example: ``wget -O - '%s'``. + +cmd-sum + Command used for checksum calculation. + Command output and ``--sum`` parameter must match. + Parameter will be used for ``printf`` and must include only one + format specification ``%s`` which will become destination. + For example: ``md5sum '%s' | awk '{print $1}'``. + +onchange + Execute this command after download. + + +EXAMPLES +-------- + +.. code-block:: sh + + __directory /opt/cpma + + require='__directory/opt/cpma' \ + __download /opt/cpma/cnq3.zip \ + --url https://cdn.playmorepromode.com/files/cnq3/cnq3-1.51.zip \ + --sum md5:46da3021ca9eace277115ec9106c5b46 + + require='__download/opt/cpma/cnq3.zip' \ + __unpack /opt/cpma/cnq3.zip \ + --move-existing-destination \ + --destination /opt/cpma/server + + +AUTHORS +------- +Ander Punnar + + +COPYING +------- +Copyright \(C) 2020 Ander Punnar. 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/__download/manifest b/cdist/conf/type/__download/manifest new file mode 100755 index 00000000..7ec8d86d --- /dev/null +++ b/cdist/conf/type/__download/manifest @@ -0,0 +1,6 @@ +#!/bin/sh -e + +if grep -Eq '^wget' "$__object/explorer/remote_cmd" +then + __package wget +fi diff --git a/cdist/conf/type/__download/parameter/default/download b/cdist/conf/type/__download/parameter/default/download new file mode 100644 index 00000000..40830374 --- /dev/null +++ b/cdist/conf/type/__download/parameter/default/download @@ -0,0 +1 @@ +local diff --git a/cdist/conf/type/__download/parameter/optional b/cdist/conf/type/__download/parameter/optional new file mode 100644 index 00000000..838e2fbf --- /dev/null +++ b/cdist/conf/type/__download/parameter/optional @@ -0,0 +1,4 @@ +cmd-get +cmd-sum +download +onchange diff --git a/cdist/conf/type/__download/parameter/required b/cdist/conf/type/__download/parameter/required new file mode 100644 index 00000000..6ea4c38f --- /dev/null +++ b/cdist/conf/type/__download/parameter/required @@ -0,0 +1,2 @@ +url +sum diff --git a/cdist/conf/type/__file/explorer/stat b/cdist/conf/type/__file/explorer/stat index 91c8cc84..29b3c8a3 100755 --- a/cdist/conf/type/__file/explorer/stat +++ b/cdist/conf/type/__file/explorer/stat @@ -31,10 +31,10 @@ fallback() { gid=$(echo "$ls_line" | awk '{ print $4 }') owner=$(awk -F: -v uid="$uid" '$3 == uid { print $1; f=1 } END { if (!f) print "UNKNOWN" }' /etc/passwd) - group=$(awk -F: -v uid="$uid" '$3 == uid { print $1; f=1 } END { if (!f) print "UNKNOWN" }' /etc/group) + group=$(awk -F: -v gid="$gid" '$3 == gid { print $1; f=1 } END { if (!f) print "UNKNOWN" }' /etc/group) mode_text=$(echo "$ls_line" | awk '{ print $1 }') - mode=$(echo "$mode_text" | awk '{ k=0; for (i=0; i<=8; i++) k += ((substr($1, i+2, 1) ~ /[rwx]/) * 2^(8-i)); printf("%0o", k) }') + mode=$(echo "$mode_text" | awk '{for(i=8;i>=0;--i){c=substr($1,10-i,1);k+=((c~/[rwxst]/)*2^i);if(!(i%3))k+=(tolower(c)~/[lst]/)*2^(9+i/3)}printf("%04o",k)}') size=$(echo "$ls_line" | awk '{ print $5 }') links=$(echo "$ls_line" | awk '{ print $2 }') @@ -53,64 +53,32 @@ fallback() { [ -e "$destination" ] || exit 0 -if ! command -v stat >/dev/null -then +command -v stat >/dev/null 2>&1 || { fallback exit -fi +} case $("$__explorer/os") in freebsd|netbsd|openbsd|macosx) - stat -f "type: %HT + stat -f 'type: %HT owner: %Du %Su group: %Dg %Sg -mode: %Lp %Sp +mode: %Mp%03Lp %Sp size: %Dz links: %Dl -" "$destination" | awk '/^type/ { print tolower($0); next } { print }' +' "$destination" | awk '/^type/ { print tolower($0); next } { print }' ;; - solaris) - ls1="$( ls -ld "$destination" )" - ls2="$( ls -ldn "$destination" )" - - if [ -f "$__object/parameter/mode" ] - then mode_should="$( cat "$__object/parameter/mode" )" - fi - - # yes, it is ugly hack, but if you know better way... - if [ -z "$( find "$destination" -perm "$mode_should" )" ] - then octets=888 - else octets="$( echo "$mode_should" | sed 's/^0//' )" - fi - - case "$( echo "$ls1" | cut -c1-1 )" in - -) echo 'type: regular file' ;; - d) echo 'type: directory' ;; - esac - - echo "owner: $( echo "$ls2" \ - | awk '{print $3}' ) $( echo "$ls1" \ - | awk '{print $3}' )" - - echo "group: $( echo "$ls2" \ - | awk '{print $4}' ) $( echo "$ls1" \ - | awk '{print $4}' )" - - echo "mode: $octets $( echo "$ls1" | awk '{print $1}' )" - echo "size: $( echo "$ls1" | awk '{print $5}' )" - echo "links: $( echo "$ls1" | awk '{print $2}' )" - ;; *) # NOTE: Do not use --printf here as it is not supported by BusyBox stat. # NOTE: BusyBox's stat might not support the "-c" option, in which case # we fall through to the shell fallback. - stat -c "type: %F + stat -c 'type: %F owner: %u %U group: %g %G -mode: %a %A +mode: %04a %A size: %s -links: %h" "$destination" 2>/dev/null || fallback - ;; +links: %h' "$destination" 2>/dev/null || fallback + ;; esac diff --git a/cdist/conf/type/__file/gencode-remote b/cdist/conf/type/__file/gencode-remote index 815593bd..35356b13 100755 --- a/cdist/conf/type/__file/gencode-remote +++ b/cdist/conf/type/__file/gencode-remote @@ -68,9 +68,11 @@ case "$state_should" in if [ -f "$__object/parameter/$attribute" ]; then value_should="$(cat "$__object/parameter/$attribute")" - # change 0xxx format to xxx format => same as stat returns + # format mode in four digits => same as stat returns if [ "$attribute" = mode ]; then - value_should="$(echo "$value_should" | sed 's/^0\(...\)/\1/')" + # Convert to four-digit octal number (printf interprets + # strings with leading 0s as octal!) + value_should=$(printf '%04o' "0${value_should}") fi value_is="$(get_current_value "$attribute" "$value_should")" diff --git a/cdist/conf/type/__file/man.rst b/cdist/conf/type/__file/man.rst index 7a0603bb..2f3b9e69 100644 --- a/cdist/conf/type/__file/man.rst +++ b/cdist/conf/type/__file/man.rst @@ -50,13 +50,13 @@ state create or modify it group - Group to chgrp to. + Group to chgrp to. Defaults to ``root``. mode - Unix permissions, suitable for chmod. + Unix permissions, suitable for chmod. Defaults to a very secure ``0600``. owner - User to chown to. + User to chown to. Defaults to ``root``. source If supplied, copy this file from the host running cdist to the target. diff --git a/cdist/conf/type/__filesystem/explorer/lsblk b/cdist/conf/type/__filesystem/explorer/lsblk index 9ae544ac..9be3c575 100644 --- a/cdist/conf/type/__filesystem/explorer/lsblk +++ b/cdist/conf/type/__filesystem/explorer/lsblk @@ -18,16 +18,16 @@ # along with cdist. If not, see . # -os=$("$__explorer/os") +os=$("${__explorer:?}/os") -if [ -f "$__object/parameter/device" ]; then +if [ -f "${__object:?}/parameter/device" ]; then blkdev="$(cat "$__object/parameter/device")" else - blkdev="$__object_id" + blkdev="${__object_id:?}" fi case "$os" in - centos|fedora|redhat|suse|gentoo) + alpine|centos|fedora|redhat|suse|gentoo) if [ ! -x "$(command -v lsblk)" ]; then echo "lsblk is required for __filesystem type" >&2 exit 1 diff --git a/cdist/conf/type/__group/gencode-remote b/cdist/conf/type/__group/gencode-remote index 6091c548..ff63e218 100755 --- a/cdist/conf/type/__group/gencode-remote +++ b/cdist/conf/type/__group/gencode-remote @@ -88,7 +88,7 @@ if [ "$state" = "present" ]; then fi done if [ "$os" = "freebsd" ]; then - echo pw groupadd "$@" "$name" + echo pw groupadd "$name" "$@" else echo groupadd "$@" "$name" fi diff --git a/cdist/conf/type/__hosts/man.rst b/cdist/conf/type/__hosts/man.rst index bece7967..1ac706cb 100644 --- a/cdist/conf/type/__hosts/man.rst +++ b/cdist/conf/type/__hosts/man.rst @@ -25,6 +25,10 @@ ip state is ``present``, this parameter is mandatory, if state is ``absent``, this parameter is silently ignored. +alias + An alias for the hostname. + This parameter can be specified multiple times (once per alias). + EXAMPLES -------- @@ -36,6 +40,8 @@ EXAMPLES # previously configured via __hosts. __hosts happy --state absent + __hosts srv1.example.com --ip 192.168.0.42 --alias srv1 + SEE ALSO -------- @@ -43,13 +49,14 @@ SEE ALSO AUTHORS ------- - -Dmitry Bogatov +| Dmitry Bogatov +| Dennis Camera COPYING ------- -Copyright (C) 2015,2016 Dmitry Bogatov. Free use of this software is granted -under the terms of the GNU General Public License version 3 or later -(GPLv3+). +Copyright \(C) 2015-2016 Dmitry Bogatov, 2019 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/__hosts/manifest b/cdist/conf/type/__hosts/manifest index c536b83b..8103ebd5 100755 --- a/cdist/conf/type/__hosts/manifest +++ b/cdist/conf/type/__hosts/manifest @@ -1,29 +1,42 @@ #!/bin/sh -e -# Copyright (C) 2015 Bogatov Dmitry # -# This program is free software: you can redistribute it and/or modify +# Copyright (C) 2015 Bogatov Dmitry +# 2019 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. # -# This program is distributed in the hope that it will be useful, +# 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 this program. If not, see . -set -ue +# -hostname="$__object_id" -state="$(cat "$__object/parameter/state")" -marker="# __hosts/$hostname" +set -e -set -- "__hosts/$hostname" --file /etc/hosts --state "$state" +hostname=$__object_id +state=$(cat "${__object}/parameter/state") +marker="# __hosts/${hostname}" -if [ "$state" = absent ] ; then - __line "$@" --regex "$marker" +if test "${state}" != 'absent' +then + ip=$(cat "${__object}/parameter/ip") + if test -s "${__object}/parameter/alias" + then + aliases=$(while read -r a; do printf '\t%s' "$a"; done <"$__object/parameter/alias") + fi + + set -- --line "$(printf '%s\t%s%s %s' \ + "${ip}" "${hostname}" "${aliases}" "${marker}")" else - ip="$(cat "$__object/parameter/ip")" - __line "$@" --line "$ip $hostname $marker" + set -- --regex "$(echo "${marker}" | sed -e 's/\./\\./')$" fi + +__line "/etc/hosts:${hostname}" --file /etc/hosts --state "${state}" "$@" diff --git a/cdist/conf/type/__hosts/parameter/optional_multiple b/cdist/conf/type/__hosts/parameter/optional_multiple new file mode 100644 index 00000000..d077ed80 --- /dev/null +++ b/cdist/conf/type/__hosts/parameter/optional_multiple @@ -0,0 +1 @@ +alias diff --git a/cdist/conf/type/__key_value/explorer/state b/cdist/conf/type/__key_value/explorer/state index 7b2de1df..d24600af 100755 --- a/cdist/conf/type/__key_value/explorer/state +++ b/cdist/conf/type/__key_value/explorer/state @@ -40,7 +40,9 @@ else fi export key state delimiter value exact_delimiter -awk -f - "$file" <<"AWK_EOF" +awk_bin=$(PATH=$(getconf PATH 2>/dev/null) && command -v awk || echo awk) + +"${awk_bin}" -f - "$file" <<"AWK_EOF" BEGIN { state=ENVIRON["state"] key=ENVIRON["key"] diff --git a/cdist/conf/type/__key_value/files/remote_script.sh b/cdist/conf/type/__key_value/files/remote_script.sh index f7a1add5..faf080cb 100644 --- a/cdist/conf/type/__key_value/files/remote_script.sh +++ b/cdist/conf/type/__key_value/files/remote_script.sh @@ -24,7 +24,10 @@ if [ -f "$file" ]; then else touch "$file" fi -awk -f - "$file" >"$tmpfile" <<"AWK_EOF" + +awk_bin=$(PATH=$(getconf PATH 2>/dev/null) && command -v awk || echo awk) + +"${awk_bin}" -f - "$file" >"$tmpfile" <<"AWK_EOF" BEGIN { # import variables in a secure way .. state=ENVIRON["state"] diff --git a/cdist/conf/type/__key_value/gencode-remote b/cdist/conf/type/__key_value/gencode-remote index 13cc27c7..1174400e 100755 --- a/cdist/conf/type/__key_value/gencode-remote +++ b/cdist/conf/type/__key_value/gencode-remote @@ -25,7 +25,7 @@ state_should="$(cat "$__object/parameter/state")" state_is="$(cat "$__object/explorer/state")" fire_onchange='' -if [ "$state_is" = "$state_should" ]; then +if [ "$state_is" = "$state_should" ]; then exit 0 fi diff --git a/cdist/conf/type/__letsencrypt_cert/manifest b/cdist/conf/type/__letsencrypt_cert/manifest index 68ecf9d4..b4464366 100755 --- a/cdist/conf/type/__letsencrypt_cert/manifest +++ b/cdist/conf/type/__letsencrypt_cert/manifest @@ -91,6 +91,9 @@ if [ -z "${certbot_fullpath}" ]; then certbot_fullpath=/usr/local/bin/certbot ;; + ubuntu) + __package certbot + ;; *) echo "Unsupported os: $os" >&2 exit 1 diff --git a/cdist/conf/type/__link/man.rst b/cdist/conf/type/__link/man.rst index fe0ce425..2e81aea9 100644 --- a/cdist/conf/type/__link/man.rst +++ b/cdist/conf/type/__link/man.rst @@ -18,7 +18,7 @@ source Specifies the link source. type - Specifies the link type: Either hard or symoblic. + Specifies the link type: Either hard or symbolic. OPTIONAL PARAMETERS diff --git a/cdist/conf/type/__locale_system/manifest b/cdist/conf/type/__locale_system/manifest index 80f7401b..4b996ebc 100755 --- a/cdist/conf/type/__locale_system/manifest +++ b/cdist/conf/type/__locale_system/manifest @@ -3,6 +3,7 @@ # 2012-2016 Steven Armstrong (steven-cdist at armstrong.cc) # 2016 Carlos Ortigoza (carlos.ortigoza at ungleich.ch) # 2016 Nico Schottelius (nico.schottelius at ungleich.ch) +# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch) # # This file is part of cdist. # @@ -23,17 +24,171 @@ # Configure system-wide locale by modifying i18n file. # +version_ge() { + awk -F '[^0-9.]' -v target="${1:?}" ' + function max(x, y) { return x > y ? x : y } + BEGIN { + getline + nx = split($1, x, ".") + ny = split(target, y, ".") + for (i = 1; i <= max(nx, ny); ++i) { + diff = int(x[i]) - int(y[i]) + if (diff == 0) continue + exit (diff < 0) + } + }' +} + + +key=$__object_id +onchange_cmd= # none, by default +quote_value=false + +catval() { + # shellcheck disable=SC2059 + printf "$($quote_value && echo '"%s"' || echo '%s')" "$(cat "$1")" +} + +state_should=$(cat "${__object}/parameter/state") + os=$(cat "$__global/explorer/os") -case "$os" in - debian|ubuntu) +case $os +in + debian) + if version_ge 4 <"${__global}/explorer/os_version" + then + # Debian 4 (etch) and later + locale_conf="/etc/default/locale" + else + locale_conf="/etc/environment" + fi + ;; + devuan) locale_conf="/etc/default/locale" ;; + ubuntu) + if version_ge 6.10 <"${__global}/explorer/os_version" + then + # Ubuntu 6.10 (edgy) and later + locale_conf="/etc/default/locale" + else + locale_conf="/etc/environment" + fi + ;; archlinux) locale_conf="/etc/locale.conf" ;; - redhat|centos) - locale_conf="/etc/sysconfig/i18n" + centos|redhat|scientific) + # shellcheck source=/dev/null + version_id=$(. "${__global}/explorer/os_release" && echo "${VERSION_ID:-0}") + if echo "${version_id}" | version_ge 7 + then + locale_conf="/etc/locale.conf" + else + locale_conf="/etc/sysconfig/i18n" + fi + ;; + fedora) + # shellcheck source=/dev/null + version_id=$(. "${__global}/explorer/os_release" && echo "${VERSION_ID:-0}") + if echo "${version_id}" | version_ge 18 + then + locale_conf="/etc/locale.conf" + quote_value=false + else + locale_conf="/etc/sysconfig/i18n" + fi + ;; + gentoo) + case $(cat "${__global}/explorer/init") + in + (*openrc*) + locale_conf="/etc/env.d/02locale" + onchange_cmd="env-update --no-ldconfig" + quote_value=true + ;; + (systemd) + locale_conf="/etc/locale.conf" + ;; + esac + ;; + freebsd|netbsd) + # NetBSD doesn't have a separate configuration file to set locales. + # In FreeBSD locales could be configured via /etc/login.conf but parsing + # that would be annoying, so the shell login file will have to do. + # "Non-POSIX" shells like csh will not be updated here. + + locale_conf="/etc/profile" + quote_value=true + value="$(catval "${__object}/parameter/value"); export ${key}" + ;; + solaris) + locale_conf="/etc/default/init" + locale_conf_group="sys" + + if version_ge 5.11 <"${__global}/explorer/os_version" + then + # mode on Oracle Solaris 11 is actually 0444, + # but the write bit makes sense, IMO + locale_conf_mode=0644 + + # Oracle Solaris 11.2 and later uses SMF to store environment info. + # This is a hack, but I didn't feel like modifying the whole type + # just for some Oracle nonsense. + # 11.3 apparently added nlsadm(1m), but it is missing from 11.2. + # Illumos continues to use /etc/default/init + # NOTE: Remember not to use "cool" POSIX features like -q or -e with + # Solaris grep. + release_regex='Oracle Solaris 11.[2-9][0-9]*' + case $state_should + in + (present) + svccfg_cmd="svccfg -s svc:/system/environment:init setprop environment/${key} = astring: '$(cat "${__object}/parameter/value")'" + ;; + (absent) + svccfg_cmd="svccfg -s svc:/system/environment:init delprop environment/${key}" + ;; + esac + refresh_cmd='svcadm refresh svc:/system/environment' + onchange_cmd="grep '${release_regex}' /etc/release >&- || exit 0; ${svccfg_cmd:-:} && ${refresh_cmd}" + else + locale_conf_mode=0555 + fi + ;; + slackware) + # NOTE: lang.csh (csh config) is ignored here. + locale_conf="/etc/profile.d/lang.sh" + locale_conf_mode=0755 + key="export ${__object_id}" + ;; + suse) + if test -s "${__global}/explorer/os_release" + then + # shellcheck source=/dev/null + os_version=$(. "${__global}/explorer/os_release" && echo "${VERSION}") + else + os_version=$(sed -n 's/^VERSION\ *=\ *//p' "${__global}/explorer/os_version") + fi + os_major=$(expr "${os_version}" : '\([0-9]\{1,\}\)') + + # https://documentation.suse.com/sles/15-SP2/html/SLES-all/cha-suse.html#sec-suse-l10n + if expr "${os_major}" '>=' 15 \& "${os_major}" != 42 + then + # It seems that starting with SuSE 15 the systemd /etc/locale.conf + # is the preferred way to set locales, although + # /etc/sysconfig/language is still available. + # Older documentation doesn't mention /etc/locale.conf, even though + # is it created when localectl is used. + locale_conf="/etc/locale.conf" + else + locale_conf="/etc/sysconfig/language" + quote_value=true + key="RC_${__object_id}" + fi + ;; + voidlinux) + locale_conf="/etc/locale.conf" ;; *) echo "Your operating system ($os) is currently not supported by this type (${__type##*/})." >&2 @@ -42,14 +197,16 @@ case "$os" in ;; esac -__file "$locale_conf" \ - --owner root --group root --mode 644 \ - --state exists +__file "${locale_conf}" --state exists \ + --owner "${locale_conf_owner:-0}" \ + --group "${locale_conf_group:-0}" \ + --mode "${locale_conf_mode:-0644}" -require="__file/$locale_conf" \ - __key_value "$locale_conf:$__object_id" \ - --file "$locale_conf" \ - --key "$__object_id" \ - --delimiter = \ - --state "$(cat "$__object/parameter/state")" \ - --value "$(cat "$__object/parameter/value")" +require="__file/${locale_conf}" \ +__key_value "${locale_conf}:${key#export }" \ + --file "${locale_conf}" \ + --key "${key}" \ + --delimiter '=' --exact_delimiter \ + --state "${state_should}" \ + --value "${value:-$(catval "${__object}/parameter/value")}" \ + --onchange "${onchange_cmd}" diff --git a/cdist/conf/type/__motd/gencode-remote b/cdist/conf/type/__motd/gencode-remote index 738ea834..cb7bfc84 100755 --- a/cdist/conf/type/__motd/gencode-remote +++ b/cdist/conf/type/__motd/gencode-remote @@ -22,13 +22,6 @@ os=$(cat "$__global/explorer/os") case "$os" in - debian|ubuntu|devuan) - - # Debian and Ubuntu need to be updated, - # as seen in /etc/init.d/bootlogs - echo "uname -snrvm > /var/run/motd" - echo "cat /etc/motd.tail >> /var/run/motd" - ;; freebsd) # FreeBSD only updates /etc/motd on boot, # as seen in /etc/rc.d/motd diff --git a/cdist/conf/type/__motd/manifest b/cdist/conf/type/__motd/manifest index ded734d7..b8f74ebf 100755 --- a/cdist/conf/type/__motd/manifest +++ b/cdist/conf/type/__motd/manifest @@ -33,10 +33,6 @@ os=$(cat "$__global/explorer/os") case "$os" in - debian|ubuntu|devuan) - # Debian-based systems use /etc/motd.tail as a template - destination=/etc/motd.tail - ;; freebsd) # FreeBSD uses motd.template to prepend system information on boot # (this actually only applies starting with version 13, diff --git a/cdist/conf/type/__openldap_server/man.rst b/cdist/conf/type/__openldap_server/man.rst index fbad21d8..fa714ec0 100644 --- a/cdist/conf/type/__openldap_server/man.rst +++ b/cdist/conf/type/__openldap_server/man.rst @@ -31,8 +31,8 @@ manager-password-hash Generate e.g. with: `slappasswd -s weneedgoodsecurity`. See `slappasswd(8C)`, `slapd.conf(5)`. TODO: implement this: http://blog.adamsbros.org/2015/06/09/openldap-ssha-salted-hashes-by-hand/ - to derive from the manager-password parameter and ensure idempotency (care with salts). - At that point, manager-password-hash should be deprecated and ignored. + to derive from the manager-password parameter and ensure idempotency (care with salts). + At that point, manager-password-hash should be deprecated and ignored. serverid The server for the directory. @@ -103,8 +103,8 @@ syncrepl-host Set once per host that will replicate the directory. module - LDAP module to load. See `slapd.conf(5)`. - Default value is OS-dependent, see manifest. + LDAP module to load. See `slapd.conf(5)`. Some dependencies might have to + be installed beforehand. Default value is OS-dependent, see manifest. schema Name of LDAP schema to load. Must be the name without extension of a diff --git a/cdist/conf/type/__openldap_server/manifest b/cdist/conf/type/__openldap_server/manifest index 84ba176f..2aeece26 100644 --- a/cdist/conf/type/__openldap_server/manifest +++ b/cdist/conf/type/__openldap_server/manifest @@ -25,6 +25,7 @@ case "${os}" in SLAPD_DATA_DIR="/var/db/openldap-data" SLAPD_RUN_DIR="/var/run/openldap" SLAPD_MODULE_PATH="/usr/local/libexec/openldap" + SLAPD_MODULE_TYPE="la" if [ -z "${slapd_modules}" ]; then # It looks like ppolicy and syncprov must be compiled slapd_modules="back_mdb back_monitor" @@ -43,13 +44,34 @@ case "${os}" in SLAPD_DATA_DIR="/var/lib/ldap" SLAPD_RUN_DIR="/var/run/slapd" SLAPD_MODULE_PATH="/usr/lib/ldap" + SLAPD_MODULE_TYPE="la" if [ -z "${slapd_modules}" ]; then slapd_modules="back_mdb ppolicy syncprov back_monitor" fi + CONF_OWNER="openldap" + CONF_GROUP="openldap" if [ -z "${tls_cipher_suite}" ]; then tls_cipher_suite="NORMAL" fi ;; + alpine) + PKGS="openldap openldap-clients" + ETC="/etc" + SLAPD_DIR="/etc/openldap" + SLAPD_DATA_DIR="/var/lib/openldap" + SLAPD_RUN_DIR="/var/run/openldap" + SLAPD_MODULE_PATH="/usr/lib/openldap" + SLAPD_MODULE_TYPE="so" + if [ -z "${slapd_modules}" ]; then + slapd_modules="back_mdb ppolicy syncprov back_monitor" + PKGS="$PKGS openldap-back-mdb openldap-back-monitor openldap-overlay-all" + fi + CONF_OWNER="ldap" + CONF_GROUP="$SLAPD_USER" + if [ -z "${tls_cipher_suite}" ]; then + tls_cipher_suite="DEFAULT" + fi + ;; *) echo "Don't know the openldap defaults for: $os" >&2 exit 1 @@ -156,6 +178,12 @@ case "${os}" in --line "SLAPD_SERVICES=\"${slapd_urls}\"" \ --state present ;; + alpine) + require="__package/${PKG_MAIN}" __line add_slapd_services \ + --file ${ETC}/conf.d/slapd \ + --line "command_args=\"-h '${slapd_urls}'\"" \ + --state present + ;; *) # Nothing to do here, move on. ;; @@ -170,20 +198,22 @@ if [ -z "${_skip_letsencrypt_cert}" ]; then fi # shellcheck disable=SC2086 - __letsencrypt_cert "${name}" --admin-email "${admin_email}" \ - --renew-hook "cp ${ETC}/letsencrypt/live/${name}/*.pem ${SLAPD_DIR}/sasl2 && chown -R openldap:openldap ${SLAPD_DIR}/sasl2 && service slapd restart" \ - --automatic-renewal ${staging} + __directory ${SLAPD_DIR}/sasl2 + require="__directory/${SLAPD_DIR}/sasl2" __letsencrypt_cert "${name}" \ + --admin-email "${admin_email}" \ + --renew-hook "cp ${ETC}/letsencrypt/live/${name}/*.pem ${SLAPD_DIR}/sasl2 && chown -R ${CONF_OWNER}:${CONF_GROUP} ${SLAPD_DIR}/sasl2 && service slapd restart" \ + --automatic-renewal "${staging}" fi require="__package/${PKG_MAIN}" __directory ${SLAPD_DIR}/slapd.d --state absent if [ -z "${_skip_letsencrypt_cert}" ]; then require="__package/${PKG_MAIN} __letsencrypt_cert/${name}" \ - __file ${SLAPD_DIR}/slapd.conf --owner ${CONF_OWNER} --group ${CONF_GROUP} --mode 644 \ + __file "${SLAPD_DIR}/slapd.conf" --owner "${CONF_OWNER}" --group "${CONF_GROUP}" --mode 644 \ --source "${ldapconf}" else require="__package/${PKG_MAIN}" \ - __file ${SLAPD_DIR}/slapd.conf --owner ${CONF_OWNER} --group ${CONF_GROUP} --mode 644 \ + __file "${SLAPD_DIR}/slapd.conf" --owner "${CONF_OWNER}" --group "${CONF_GROUP}" --mode 644 \ --source "${ldapconf}" fi @@ -210,7 +240,7 @@ done # Add specified modules echo "modulepath ${SLAPD_MODULE_PATH}" >> "${ldapconf}" for module in ${slapd_modules}; do - echo "moduleload ${module}.la" >> "${ldapconf}" + echo "moduleload ${module}.${SLAPD_MODULE_TYPE}" >> "${ldapconf}" done # Rest of the config diff --git a/cdist/conf/type/__package_apt/gencode-remote b/cdist/conf/type/__package_apt/gencode-remote index e02564a2..f3d91566 100755 --- a/cdist/conf/type/__package_apt/gencode-remote +++ b/cdist/conf/type/__package_apt/gencode-remote @@ -64,7 +64,7 @@ esac # Hint if we need to avoid questions at some point: # DEBIAN_PRIORITY=critical can reduce the number of questions -aptget="DEBIAN_FRONTEND=noninteractive apt-get --quiet --yes --no-install-recommends -o Dpkg::Options::=\"--force-confdef\" -o Dpkg::Options::=\"--force-confold\"" +aptget="DEBIAN_FRONTEND=noninteractive apt-get --quiet --yes -o APT::Install-Recommends=0 -o Dpkg::Options::=\"--force-confdef\" -o Dpkg::Options::=\"--force-confold\"" if [ "$state_is" = "$state_should" ]; then if [ -z "$version" ] || [ "$version" = "$version_is" ]; then diff --git a/cdist/conf/type/__package_opkg/explorer/pkg_status b/cdist/conf/type/__package_opkg/explorer/pkg_status index 5da4f742..de7b896b 100755 --- a/cdist/conf/type/__package_opkg/explorer/pkg_status +++ b/cdist/conf/type/__package_opkg/explorer/pkg_status @@ -1,7 +1,8 @@ -#!/bin/sh +#!/bin/sh -e # # 2011 Nico Schottelius (nico-cdist at schottelius.org) # 2012 Giel van Schijndel (giel plus cdist at mortis dot eu) +# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch) # # This file is part of cdist. # @@ -19,21 +20,78 @@ # along with cdist. If not, see . # # -# Retrieve the status of a package - parsed opkg output +# Retrieve the status of a package - parses opkg output # -if [ -f "$__object/parameter/name" ]; then - name="$(cat "$__object/parameter/name")" +readonly __type_path=${__object%%${__object_id}*} +test -d "${__type_path}" || { echo 'Cannot determine __type_path' >&2; exit 1; } +readonly LOCKFILE="${__type_path:?}/.cdist_opkg.lock" + +if command -v flock >/dev/null 2>&1 +then + # use flock (if available) on FD 9 + _lock() { + exec 9<>"${LOCKFILE:?}" + flock -x 9 + echo $$>&9 + } + _unlock() { + :>"${LOCKFILE:?}" + flock -u 9 + exec 9<&- + } else - name="$__object_id" + # fallback to mkdir if flock is missing + _lock() { + until mkdir "${LOCKFILE:?}.dir" 2>/dev/null + do + while test -d "${LOCKFILE}.dir" + do + # DEBUG: + # printf 'Locked by PID: %u\n' "$(cat "${LOCKFILE}.dir/pid")" + sleep 1 + done + done + echo $$ >"${LOCKFILE:?}.dir/pid" + } + _unlock() { + test -d "${LOCKFILE}.dir" || return 0 + if test -s "${LOCKFILE}.dir/pid" + then + test "$(cat "${LOCKFILE}.dir/pid")" = $$ || return 1 + rm "${LOCKFILE:?}.dir/pid" + fi + rmdir "${LOCKFILE:?}.dir" + } fi -# Except dpkg failing, if package is not known / installed -if opkg status "$name" 2>/dev/null | grep -q "^Status: install user installed$"; then - echo "present" - exit 0 -elif [ "$(opkg info "$name" 2> /dev/null | wc -l)" -eq 0 ]; then - echo "absent notpresent" - exit 0 + +if test -f "${__object}/parameter/name" +then + pkg_name=$(cat "${__object}/parameter/name") +else + pkg_name=$__object_id +fi + + +# NOTE: We need to lock parallel execution of type explorers and code-remote +# because opkg will try to acquire the OPKG lock (usually /var/lock/opkg.lock) +# using lockf(2) for every operation. +# It will not wait for the lock but terminate with an error. +# This leads to incorrect 'absent notpresent' statuses when parallel execution +# is enabled. +trap _unlock EXIT +_lock + + +# Except opkg failing, if package is not known / installed +if opkg status "${pkg_name}" 2>/dev/null \ + | grep -q -e '^Status: [^ ][^ ]* [^ ][^ ]* installed$' +then + echo 'present' +elif opkg info "${pkg_name}" 2>/dev/null | grep -q . +then + echo 'absent notpresent' +else + echo 'absent' fi -echo "absent" diff --git a/cdist/conf/type/__package_opkg/gencode-remote b/cdist/conf/type/__package_opkg/gencode-remote index 269d5f49..28caff71 100755 --- a/cdist/conf/type/__package_opkg/gencode-remote +++ b/cdist/conf/type/__package_opkg/gencode-remote @@ -2,6 +2,7 @@ # # 2011,2013 Nico Schottelius (nico-cdist at schottelius.org) # 2012 Giel van Schijndel (giel plus cdist at mortis dot eu) +# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch) # # This file is part of cdist. # @@ -19,41 +20,50 @@ # along with cdist. If not, see . # # -# Manage packages on OpenWRT and co. +# Manage packages on OpenWrt, optware, and co. # -if [ -f "$__object/parameter/name" ]; then - name="$(cat "$__object/parameter/name")" +if test -f "${__object}/parameter/name" +then + name=$(cat "${__object}/parameter/name") else - name="$__object_id" + name=$__object_id fi -state_should="$(cat "$__object/parameter/state")" +state_should=$(cat "${__object}/parameter/state") +state_is=$(cat "${__object}/explorer/pkg_status") -state_is="$(cat "$__object/explorer/pkg_status")" -case "$state_is" in - absent*) - present="$(echo "$state_is" | cut -d ' ' -f 2)" - state_is="absent" - ;; +case $state_is +in + (absent*) + presence=$(echo "${state_is}" | cut -d ' ' -f 2) + state_is='absent' + ;; esac -[ "$state_is" = "$state_should" ] && exit 0 +if test "${state_is}" = "${state_should}" +then + exit 0 +fi -case "$state_should" in - present) - if [ "$present" = "notpresent" ]; then - echo "opkg --verbosity=0 update" - fi - echo "opkg --verbosity=0 install '$name'" - echo "installed" >> "$__messages_out" - ;; - absent) - echo "opkg --verbosity=0 remove '$name'" - echo "removed" >> "$__messages_out" - ;; - *) - echo "Unknown state: ${state_should}" >&2 - exit 1 - ;; + +case $state_should +in + (present) + if test "${presence}" = 'notpresent' + then + echo 'opkg --verbosity=0 update' + fi + + printf "opkg --verbosity=0 install '%s'\n" "${name}" + echo 'installed' >>"${__messages_out}" + ;; + (absent) + printf "opkg --verbosity=0 remove '%s'" "${name}" + echo 'removed' >>"${__messages_out}" + ;; + (*) + printf 'Unknown state: %s\n' "${state_should}" >&2 + exit 1 + ;; esac diff --git a/cdist/conf/type/__package_pip/explorer/pip b/cdist/conf/type/__package_pip/explorer/pip new file mode 100755 index 00000000..cf9fae89 --- /dev/null +++ b/cdist/conf/type/__package_pip/explorer/pip @@ -0,0 +1,10 @@ +#!/bin/sh -e + +for bin in pip3 pip +do + if check="$( command -v "$bin" )" + then + echo "$check" + break + fi +done diff --git a/cdist/conf/type/__package_pip/explorer/state b/cdist/conf/type/__package_pip/explorer/state index 5be07280..3cc98ab9 100644 --- a/cdist/conf/type/__package_pip/explorer/state +++ b/cdist/conf/type/__package_pip/explorer/state @@ -32,7 +32,7 @@ pipparam="$__object/parameter/pip" if [ -f "$pipparam" ]; then pip=$(cat "$pipparam") else - pip="pip" + pip="$( "$__type_explorer/pip" )" fi # If there is no pip, it may get created from somebody else. diff --git a/cdist/conf/type/__package_pip/gencode-remote b/cdist/conf/type/__package_pip/gencode-remote index dcc4fdf9..a1375c2d 100755 --- a/cdist/conf/type/__package_pip/gencode-remote +++ b/cdist/conf/type/__package_pip/gencode-remote @@ -38,7 +38,12 @@ pipparam="$__object/parameter/pip" if [ -f "$pipparam" ]; then pip=$(cat "$pipparam") else - pip="pip" + pip="$( cat "$__object/explorer/pip" )" + if [ -z "$pip" ] + then + echo 'pip not found in path' >&2 + exit 1 + fi fi runasparam="$__object/parameter/runas" @@ -55,7 +60,7 @@ case "$state_should" in then echo "su -c '$pip install -q $name' $runas" else - echo $pip install -q "$name" + echo "$pip" install -q "$name" fi echo "installed" >> "$__messages_out" ;; @@ -64,7 +69,7 @@ case "$state_should" in then echo "su -c '$pip uninstall -q -y $name' $runas" else - echo $pip uninstall -q -y "$name" + echo "$pip" uninstall -q -y "$name" fi echo "removed" >> "$__messages_out" ;; diff --git a/cdist/conf/type/__package_pkgng_freebsd/explorer/pkg_bootstrapped b/cdist/conf/type/__package_pkgng_freebsd/explorer/pkg_bootstrapped new file mode 100755 index 00000000..429f15d3 --- /dev/null +++ b/cdist/conf/type/__package_pkgng_freebsd/explorer/pkg_bootstrapped @@ -0,0 +1,4 @@ +#!/bin/sh -e +if pkg -N >/dev/null 2>&1; then + echo "YES" +fi diff --git a/cdist/conf/type/__package_pkgng_freebsd/explorer/pkg_version b/cdist/conf/type/__package_pkgng_freebsd/explorer/pkg_version index 92ce0623..f0fb9127 100755 --- a/cdist/conf/type/__package_pkgng_freebsd/explorer/pkg_version +++ b/cdist/conf/type/__package_pkgng_freebsd/explorer/pkg_version @@ -21,6 +21,11 @@ # Retrieve the status of a package - parsed dpkg output # +if ! pkg -N >/dev/null 2>&1; then + # Nothing to do if pkg is not bootstrapped + exit +fi + if [ -f "$__object/parameter/name" ]; then name="$(cat "$__object/parameter/name")" else diff --git a/cdist/conf/type/__package_pkgng_freebsd/gencode-remote b/cdist/conf/type/__package_pkgng_freebsd/gencode-remote index dd36efda..b5944177 100755 --- a/cdist/conf/type/__package_pkgng_freebsd/gencode-remote +++ b/cdist/conf/type/__package_pkgng_freebsd/gencode-remote @@ -43,6 +43,7 @@ fi repo="$(cat "$__object/parameter/repo")" state="$(cat "$__object/parameter/state")" curr_version="$(cat "$__object/explorer/pkg_version")" +pkg_bootstrapped="$(cat "$__object/explorer/pkg_bootstrapped")" add_cmd="pkg install -y" rm_cmd="pkg delete -y" upg_cmd="pkg upgrade -y" @@ -73,6 +74,10 @@ execcmd(){ ;; esac + if [ -z "${pkg_bootstrapped}" ]; then + echo "pkg bootstrap -y >/dev/null 2>&1" + fi + echo "$_cmd >/dev/null 2>&1" # Silence the output of the command echo "status=\$?" echo "if [ \"\$status\" -ne \"0\" ]; then" diff --git a/cdist/conf/type/__pf_apply/deprecated b/cdist/conf/type/__pf_apply/deprecated deleted file mode 100644 index 36cfed90..00000000 --- a/cdist/conf/type/__pf_apply/deprecated +++ /dev/null @@ -1 +0,0 @@ -Consider moving to __pf_apply_anchor. Get in touch if you need __pf_apply. diff --git a/cdist/conf/type/__pf_apply/explorer/rcvar b/cdist/conf/type/__pf_apply/explorer/rcvar deleted file mode 100755 index 7c8d535f..00000000 --- a/cdist/conf/type/__pf_apply/explorer/rcvar +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh -# -# 2012 Jake Guffey (jake.guffey at eprotex.com) -# -# 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 . -# -# -# Get the location of the pf ruleset on the target host. -# - -# Debug -#exec >&2 -#set -x - -# Check /etc/rc.conf for pf's configuration file name. Default to /etc/pf.conf - -RC="/etc/rc.conf" -PFCONF="$(grep '^pf_rules=' ${RC} | cut -d= -f2 | sed 's/"//g')" -echo "${PFCONF:-"/etc/pf.conf"}" - -# Debug -#set +x - diff --git a/cdist/conf/type/__pf_apply/gencode-remote b/cdist/conf/type/__pf_apply/gencode-remote deleted file mode 100755 index c8f7a25a..00000000 --- a/cdist/conf/type/__pf_apply/gencode-remote +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/sh -e -# -# 2012 Jake Guffey (jake.guffey at eprotex.com) -# -# 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 . -# -# -# Apply pf(4) ruleset on *BSD -# - -# Debug -#exec >&2 -#set -x - -rcvar=$(cat "$__object/explorer/rcvar") - -cat <&2 - fi -fi -EOF - -# Debug -#set +x - diff --git a/cdist/conf/type/__pf_apply/man.rst b/cdist/conf/type/__pf_apply/man.rst deleted file mode 100644 index eee345e7..00000000 --- a/cdist/conf/type/__pf_apply/man.rst +++ /dev/null @@ -1,55 +0,0 @@ -cdist-type__pf_apply(7) -======================= - -NAME ----- -cdist-type__pf_apply - Apply pf(4) ruleset on \*BSD - - -DESCRIPTION ------------ -This type is used on \*BSD systems to manage the pf firewall's active ruleset. - - -REQUIRED PARAMETERS -------------------- -NONE - - -OPTIONAL PARAMETERS -------------------- -NONE - - -EXAMPLES --------- - -.. code-block:: sh - - # Modify the ruleset on $__target_host: - __pf_ruleset --state present --source /my/pf/ruleset.conf - require="__pf_ruleset" \ - __pf_apply - - # Remove the ruleset on $__target_host (implies disabling pf(4): - __pf_ruleset --state absent - require="__pf_ruleset" \ - __pf_apply - - -SEE ALSO --------- -:strong:`pf`\ (4), :strong:`cdist-type__pf_ruleset`\ (7) - - -AUTHORS -------- -Jake Guffey - - -COPYING -------- -Copyright \(C) 2012 Jake Guffey. 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/__postfix_master/gencode-remote b/cdist/conf/type/__postfix_master/gencode-remote index 7c109a69..73de1088 100755 --- a/cdist/conf/type/__postfix_master/gencode-remote +++ b/cdist/conf/type/__postfix_master/gencode-remote @@ -67,7 +67,7 @@ case "$state_should" in remove_entry fi cat << DONE -cat >> "$config" << ${__type##*/}_DONE +cat >> "$config" << "${__type##*/}_DONE" $(cat "$entry") ${__type##*/}_DONE DONE diff --git a/cdist/conf/type/__postfix_master/parameter/optional b/cdist/conf/type/__postfix_master/parameter/optional index 792b42c5..410482b8 100644 --- a/cdist/conf/type/__postfix_master/parameter/optional +++ b/cdist/conf/type/__postfix_master/parameter/optional @@ -4,6 +4,5 @@ unpriv chroot wakeup maxproc -option comment state diff --git a/cdist/conf/type/__postfix_master/parameter/optional_multiple b/cdist/conf/type/__postfix_master/parameter/optional_multiple new file mode 100644 index 00000000..01925a15 --- /dev/null +++ b/cdist/conf/type/__postfix_master/parameter/optional_multiple @@ -0,0 +1 @@ +option diff --git a/cdist/conf/type/__pyvenv/gencode-remote b/cdist/conf/type/__pyvenv/gencode-remote index 9c7b7fab..c5b64eff 100755 --- a/cdist/conf/type/__pyvenv/gencode-remote +++ b/cdist/conf/type/__pyvenv/gencode-remote @@ -1,6 +1,7 @@ #!/bin/sh -e # # 2016 Darko Poljak (darko.poljak at gmail.com) +# 2020 Nico Schotetlius (nico.schottelius at ungleich.ch) # # This file is part of cdist. # @@ -45,7 +46,7 @@ then pyvenv=$(cat "$pyvenvparam") else case "$os" in - alpine) # no pyvenv on alpine - I assume others will follow + alpine|ubuntu) # no pyvenv on alpine - I assume others will follow pyvenv="python3 -m venv" ;; *) diff --git a/cdist/conf/type/__pyvenv/man.rst b/cdist/conf/type/__pyvenv/man.rst index d7de92fa..8085ff12 100644 --- a/cdist/conf/type/__pyvenv/man.rst +++ b/cdist/conf/type/__pyvenv/man.rst @@ -9,7 +9,7 @@ cdist-type__pyvenv - Create or remove python virtual environment DESCRIPTION ----------- This cdist type allows you to create or remove python virtual -environment using pyvenv. +environment using pyvenv on python3 -m venv. It assumes pyvenv is already installed. Concrete package depends on concrete OS and/or OS version/distribution. Ensure this for e.g. in your init manifest as in the following example: @@ -57,7 +57,7 @@ EXAMPLES __pyvenv /home/services/djangoenv - # Use specific pyvenv + # Use specific pyvenv __pyvenv /home/foo/fooenv --pyvenv /usr/local/bin/pyvenv-3.4 # Create python virtualenv for user foo. @@ -76,4 +76,3 @@ COPYING ------- Copyright \(C) 2016 Darko Poljak. Free use of this software is granted under the terms of the GNU General Public License v3 or later (GPLv3+). - diff --git a/cdist/conf/type/__ssh_authorized_key/man.rst b/cdist/conf/type/__ssh_authorized_key/man.rst index 087a3dae..5bae02aa 100644 --- a/cdist/conf/type/__ssh_authorized_key/man.rst +++ b/cdist/conf/type/__ssh_authorized_key/man.rst @@ -15,25 +15,27 @@ This type was created to be used by the __ssh_authorized_keys type. REQUIRED PARAMETERS ------------------- file - the authorized_keys file to which the given key should be added + The authorized_keys file where the given key should be managed. key - a string containing the ssh keytype, base 64 encoded key and optional - trailing comment which shall be added to the given authorized_keys file. + The ssh key which shall be managed in this authorized_keys file. + Must be a string containing the ssh keytype, base 64 encoded key and + optional trailing comment which shall be added to the given + authorized_keys file. OPTIONAL PARAMETERS ------------------- comment - explicit comment instead of the one which may be trailing the given key + Use this comment instead of the one which may be trailing in the key. option - an option to set for this authorized_key entry. + An option to set for this authorized_key entry. Can be specified multiple times. See sshd(8) for available options. state - if the given keys should be 'present' or 'absent', defaults to 'present'. + If the managed key should be 'present' or 'absent', defaults to 'present'. MESSAGES @@ -64,7 +66,7 @@ EXAMPLES SEE ALSO -------- -:strong:`cdist__ssh_authorized_keys`\ (7), :strong:`sshd`\ (8) +:strong:`cdist-type__ssh_authorized_keys`\ (7), :strong:`sshd`\ (8) AUTHORS diff --git a/cdist/conf/type/__ssh_authorized_keys/explorer/keys b/cdist/conf/type/__ssh_authorized_keys/explorer/keys new file mode 100755 index 00000000..cec25746 --- /dev/null +++ b/cdist/conf/type/__ssh_authorized_keys/explorer/keys @@ -0,0 +1,9 @@ +#!/bin/sh -e + +# shellcheck disable=SC1090 +file="$( . "$__type_explorer/file" )" + +if [ -f "$file" ] +then + cat "$file" +fi diff --git a/cdist/conf/type/__ssh_authorized_keys/man.rst b/cdist/conf/type/__ssh_authorized_keys/man.rst index ba310ff9..dac6adeb 100644 --- a/cdist/conf/type/__ssh_authorized_keys/man.rst +++ b/cdist/conf/type/__ssh_authorized_keys/man.rst @@ -20,42 +20,48 @@ then left to the user to ensure that the file exists and that ownership and permissions work with ssh. -REQUIRED PARAMETERS -------------------- +REQUIRED MULTIPLE PARAMETERS +---------------------------- key - the ssh key which shall be added to this authorized_keys file. - Must be a string and can be specified multiple times. + An ssh key which shall be managed in this authorized_keys file. + Must be a string containing the ssh keytype, base 64 encoded key and + optional trailing comment which shall be added to the given + authorized_keys file. + Can be specified multiple times. OPTIONAL PARAMETERS ------------------- comment - explicit comment instead of the one which may be trailing the given key + Use this comment instead of the one which may be trailing in each key. file - an alternative destination file, defaults to ~$owner/.ssh/authorized_keys + An alternative destination file, defaults to ~$owner/.ssh/authorized_keys. option - an option to set for all created authorized_key entries. + An option to set for all authorized_key entries in the key parameter. Can be specified multiple times. See sshd(8) for available options. owner - the user owning the authorized_keys file, defaults to object_id. + The user owning the authorized_keys file, defaults to object_id. state - if the given keys should be 'present' or 'absent', defaults to 'present'. + If the given keys should be 'present' or 'absent', defaults to 'present'. BOOLEAN PARAMETERS ------------------ noparent - don't create or change ownership and permissions of the directory containing - the authorized_keys file + Don't create or change ownership and permissions of the directory containing + the authorized_keys file. nofile - don't manage existence, ownership and permissions of the the authorized_keys - file + Don't manage existence, ownership and permissions of the the authorized_keys + file. + +remove-unknown + Remove undefined keys. EXAMPLES @@ -67,6 +73,12 @@ EXAMPLES __ssh_authorized_keys root \ --key "$(cat ~/.ssh/id_rsa.pub)" + # same as above, but make sure your key is only key in + # root's authorized_keys file + __ssh_authorized_keys root \ + --key "$(cat ~/.ssh/id_rsa.pub)" \ + --remove-unknown + # allow key to login as user-name __ssh_authorized_keys user-name \ --key "ssh-rsa AXYZAAB3NzaC1yc2..." diff --git a/cdist/conf/type/__ssh_authorized_keys/manifest b/cdist/conf/type/__ssh_authorized_keys/manifest index b9f0582e..b319316b 100755 --- a/cdist/conf/type/__ssh_authorized_keys/manifest +++ b/cdist/conf/type/__ssh_authorized_keys/manifest @@ -55,8 +55,12 @@ _cksum() { echo "$1" | cksum | cut -d' ' -f 1 } +_type_and_key() { + echo "$1" | tr ' ' '\n' | awk '/^(ssh|ecdsa)-[^ ]+/ { printf $1" "; getline; printf $1 }' +} + while read -r key; do - type_and_key="$(echo "$key" | tr ' ' '\n' | awk '/^(ssh|ecdsa)-[^ ]+/ { printf $1" "; getline; printf $1 }')" + type_and_key="$( _type_and_key "$key" )" object_id="$(_cksum "$file")-$(_cksum "$type_and_key")" set -- "$object_id" set -- "$@" --file "$file" @@ -72,3 +76,24 @@ while read -r key; do # Ensure __ssh_authorized_key does not read stdin __ssh_authorized_key "$@" < /dev/null done < "$__object/parameter/key" + +if [ -f "$__object/parameter/remove-unknown" ] && + [ -s "$__object/explorer/keys" ] +then + while read -r key + do + type_and_key="$( _type_and_key "$key" )" + + if grep -Fq "$type_and_key" "$__object/parameter/key" + then + continue + fi + + __ssh_authorized_key "remove-$( _cksum "$file$key" )" \ + --file "$file" \ + --key "$key" \ + --state absent \ + < /dev/null + done \ + < "$__object/explorer/keys" +fi diff --git a/cdist/conf/type/__ssh_authorized_keys/parameter/boolean b/cdist/conf/type/__ssh_authorized_keys/parameter/boolean index 4bb126fe..7388fed5 100644 --- a/cdist/conf/type/__ssh_authorized_keys/parameter/boolean +++ b/cdist/conf/type/__ssh_authorized_keys/parameter/boolean @@ -1,2 +1,3 @@ noparent nofile +remove-unknown diff --git a/cdist/conf/type/__ssh_authorized_keys/parameter/optional b/cdist/conf/type/__ssh_authorized_keys/parameter/optional index 21f9bc29..fa64fc43 100644 --- a/cdist/conf/type/__ssh_authorized_keys/parameter/optional +++ b/cdist/conf/type/__ssh_authorized_keys/parameter/optional @@ -1,5 +1,4 @@ comment file -option owner state diff --git a/cdist/conf/type/__ssh_authorized_keys/parameter/optional_multiple b/cdist/conf/type/__ssh_authorized_keys/parameter/optional_multiple new file mode 100644 index 00000000..01925a15 --- /dev/null +++ b/cdist/conf/type/__ssh_authorized_keys/parameter/optional_multiple @@ -0,0 +1 @@ +option diff --git a/cdist/conf/type/__sysctl/explorer/value b/cdist/conf/type/__sysctl/explorer/value index fc85b3d8..3e93c151 100755 --- a/cdist/conf/type/__sysctl/explorer/value +++ b/cdist/conf/type/__sysctl/explorer/value @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/sh -e # # 2014 Steven Armstrong (steven-cdist at armstrong.cc) # @@ -18,5 +18,10 @@ # along with cdist. If not, see . # +if test "$(uname -s)" = NetBSD +then + PATH=$(getconf PATH) +fi + # get the current runtime value -sysctl -n "$__object_id" || true +sysctl -n "${__object_id}" || true diff --git a/cdist/conf/type/__sysctl/gencode-remote b/cdist/conf/type/__sysctl/gencode-remote index 711d54e5..f0f6deef 100755 --- a/cdist/conf/type/__sysctl/gencode-remote +++ b/cdist/conf/type/__sysctl/gencode-remote @@ -44,6 +44,8 @@ case "$os" in flag='-w' ;; netbsd) + # shellcheck disable=SC2016 + echo 'PATH=$(getconf PATH)' flag='-w' ;; freebsd|openbsd) diff --git a/cdist/conf/type/__sysctl/man.rst b/cdist/conf/type/__sysctl/man.rst index 6873003e..dbb9a1ac 100644 --- a/cdist/conf/type/__sysctl/man.rst +++ b/cdist/conf/type/__sysctl/man.rst @@ -26,6 +26,13 @@ EXAMPLES __sysctl net.ipv4.ip_forward --value 1 + # On some operating systems, e.g. NetBSD, to prevent an error if the + # MIB style name does not exist (e.g. optional kernel components), + # name and value can be separated by `?=`. The same effect can be achieved + # in cdist by appending a `?` to the key: + + __sysctl ddb.onpanic? --value -1 + AUTHORS ------- diff --git a/cdist/conf/type/__systemd_service/man.rst b/cdist/conf/type/__systemd_service/man.rst index 7eca398b..cd14c985 100644 --- a/cdist/conf/type/__systemd_service/man.rst +++ b/cdist/conf/type/__systemd_service/man.rst @@ -1,9 +1,10 @@ -cdist-type__systemd-service(7) +cdist-type__systemd_service(7) ============================== NAME ---- -cdist-type__systemd-service - Controls a systemd service state +cdist-type__systemd_service - Controls a systemd service state + DESCRIPTION ----------- @@ -14,11 +15,12 @@ service after configuration applied or shutdown one service. The activation or deactivation is out of scope. Look for the :strong:`cdist-type__systemd_util`\ (7) type instead. + REQUIRED PARAMETERS ------------------- - None. + OPTIONAL PARAMETERS ------------------- @@ -31,12 +33,12 @@ state running Service should run (default) - stoppend - Service should stopped + stopped + Service should be stopped action Executes an action on on the service. It will only execute it if the - service keeps the state **running**. There are following actions, where: + service keeps the state ``running``. There are following actions, where: reload Reloads the service @@ -48,11 +50,12 @@ BOOLEAN PARAMETERS ------------------ if-required - Only execute the action if minimum one required type outputs a message to - **$__messages_out**. Through this, the action should only executed if a + Only execute the action if at minimum one required type outputs a message + to ``$__messages_out``. Through this, the action should only executed if a dependency did something. The action will not executed if no dependencies given. + MESSAGES -------- @@ -68,12 +71,14 @@ restart reload Reloaded the service + ABORTS ------ Aborts in following cases: systemd or the service does not exist + EXAMPLES -------- .. code-block:: sh @@ -95,13 +100,15 @@ EXAMPLES # reload the service for a modified configuration file # only reloads the service if the file really changed - require="__config_file/etc/foo.conf" __systemd_service foo \ + require="__file/etc/foo.conf" __systemd_service foo \ --action reload --if-required + AUTHORS ------- Matthias Stecher + COPYRIGHT --------- Copyright \(C) 2020 Matthias Stecher. You can redistribute it diff --git a/cdist/conf/type/__timezone/gencode-remote b/cdist/conf/type/__timezone/gencode-remote index 5299f548..b685c990 100755 --- a/cdist/conf/type/__timezone/gencode-remote +++ b/cdist/conf/type/__timezone/gencode-remote @@ -22,7 +22,7 @@ # This type allows to configure the desired localtime timezone. timezone_is=$(cat "$__object/explorer/timezone_is") -timezone_should="$__object_id" +timezone_should=$(cat "$__object/parameter/tz") os=$(cat "$__global/explorer/os") if [ "$timezone_is" = "$timezone_should" ]; then diff --git a/cdist/conf/type/__timezone/man.rst b/cdist/conf/type/__timezone/man.rst index 8a945c16..6012c552 100644 --- a/cdist/conf/type/__timezone/man.rst +++ b/cdist/conf/type/__timezone/man.rst @@ -14,7 +14,8 @@ This type creates a symlink (/etc/localtime) to the selected timezone REQUIRED PARAMETERS ------------------- -None. +tz + The name of timezone to set. OPTIONAL PARAMETERS @@ -27,19 +28,24 @@ EXAMPLES .. code-block:: sh - #Set up Europe/Andorra as our timezone. - __timezone Europe/Andorra + # Set up Europe/Andorra as our timezone. + __timezone --tz Europe/Andorra - #Set up US/Central as our timezone. - __timezone US/Central + # Set up US/Central as our timezone. + __timezone --tz US/Central AUTHORS ------- -Ramon Salvadó +| Steven Armstrong +| Nico Schottelius +| Ramon Salvadó +| Dennis Camera COPYING ------- -Free use of this software is -granted under the terms of the GNU General Public License version 3 (GPLv3). +Copyright \(C) 2012-2020 the `AUTHORS`_. 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/__timezone/manifest b/cdist/conf/type/__timezone/manifest index 3d28ccba..0eb7fb9c 100755 --- a/cdist/conf/type/__timezone/manifest +++ b/cdist/conf/type/__timezone/manifest @@ -22,7 +22,7 @@ # # This type allows to configure the desired localtime timezone. -timezone="$__object_id" +timezone=$(cat "$__object/parameter/tz") os=$(cat "$__global/explorer/os") case "$os" in diff --git a/cdist/conf/type/__timezone/parameter/required b/cdist/conf/type/__timezone/parameter/required new file mode 100644 index 00000000..975445e4 --- /dev/null +++ b/cdist/conf/type/__timezone/parameter/required @@ -0,0 +1 @@ +tz diff --git a/cdist/conf/type/__pf_apply/singleton b/cdist/conf/type/__timezone/singleton similarity index 100% rename from cdist/conf/type/__pf_apply/singleton rename to cdist/conf/type/__timezone/singleton diff --git a/cdist/conf/type/__unpack/explorer/state b/cdist/conf/type/__unpack/explorer/state new file mode 100755 index 00000000..38bc0978 --- /dev/null +++ b/cdist/conf/type/__unpack/explorer/state @@ -0,0 +1,37 @@ +#!/bin/sh -e + +src="/$__object_id" + +if [ -f "$__object/parameter/sum-file" ] +then + src_sum_was_file="$( cat "$__object/parameter/sum-file" )" +else + src_sum_was_file="$src.cdist__unpack_sum" +fi + +if [ ! -f "$src" ] +then + if [ -n "$__cdist_dry_run" ] + then + echo 'mismatch' + else + echo 'missing' + fi +else + if [ ! -f "$src_sum_was_file" ] + then + echo 'mismatch' + exit 0 + fi + + src_sum_was="$( cat "$src_sum_was_file" )" + + src_sum_is="$( cksum "$src" | awk '{ print $1$2 }' )" + + if [ "$src_sum_was" = "$src_sum_is" ] + then + echo 'match' + else + echo 'mismatch' + fi +fi diff --git a/cdist/conf/type/__unpack/gencode-remote b/cdist/conf/type/__unpack/gencode-remote new file mode 100755 index 00000000..c4451f73 --- /dev/null +++ b/cdist/conf/type/__unpack/gencode-remote @@ -0,0 +1,87 @@ +#!/bin/sh -e + +if grep -Eq '^(missing|match)$' "$__object/explorer/state" +then + exit 0 +fi + +os="$( cat "$__global/explorer/os" )" + +src="/$__object_id" + +dst="$( sed 's/\/$//' "$__object/parameter/destination" )" + +cmd='' + +case "$src" in + *.tar|*.tgz|*.tar.*) + cmd="mkdir -p '$dst' && tar --directory='$dst' --extract --file='$src'" + + if [ -f "$__object/parameter/tar-strip" ] + then + tar_strip="$( cat "$__object/parameter/tar-strip" )" + + cmd="$cmd --strip-components=$tar_strip" + fi + + if [ -f "$__object/parameter/tar-extra-args" ] + then + tar_extra_args="$( cat "$__object/parameter/tar-extra-args" )" + + cmd="$cmd $tar_extra_args" + fi + ;; + *.7z) + case "$os" in + centos|fedora|redhat) + cmd='7za' + ;; + *) + cmd='7zr' + ;; + esac + + cmd="$cmd e -aoa -o'$dst' '$src'" + ;; + *.bz2) + cmd="bunzip2 --stdout '$src' > '$dst'" + ;; + *.gz) + cmd="gunzip --stdout '$src' > '$dst'" + ;; + *.lzma|*.xz) + cmd="xz --uncompress --stdout '$src' > '$dst'" + ;; + *.rar) + cmd="unrar x -o+ '$src' '$dst/'" + ;; + *.zip) + cmd="unzip -o '$src' -d '$dst'" + ;; +esac + +if [ -f "$__object/parameter/backup-destination" ] +then + echo "if [ -e '$dst' ]; then mv '$dst' '$dst.cdist__unpack_backup_$( date +%s )'; fi" +fi + +echo "$cmd" + +if [ -f "$__object/parameter/sum-file" ] +then + sum_file="$( cat "$__object/parameter/sum-file" )" +else + sum_file="$src.cdist__unpack_sum" +fi + +echo "cksum '$src' | awk '{ print \$1\$2 }' > '$sum_file'" + +if [ ! -f "$__object/parameter/preserve-archive" ] +then + echo "rm -f '$src'" +fi + +if [ -f "$__object/parameter/onchange" ] +then + cat "$__object/parameter/onchange" +fi diff --git a/cdist/conf/type/__unpack/man.rst b/cdist/conf/type/__unpack/man.rst new file mode 100644 index 00000000..daa03814 --- /dev/null +++ b/cdist/conf/type/__unpack/man.rst @@ -0,0 +1,93 @@ +cdist-type__unpack(7) +===================== + +NAME +---- +cdist-type__unpack - Unpack archives + + +DESCRIPTION +----------- +Unpack ``.tar``, ``.tgz``, ``.tar.*``, ``.7z``, ``.bz2``, ``.gz``, +``.lzma``, ``.xz``, ``.rar`` and ``.zip`` archives. Archive type is +detected by extension. + +To achieve idempotency, checksum file will be created in target. See +``--sum-file`` parameter for details. + + +REQUIRED PARAMETERS +------------------- +destination + Depending on archive format file or directory to where archive + contents will be written. + + +OPTIONAL PARAMETERS +------------------- +sum-file + Override archive's checksum file in target. By default + ``XXX.cdist__unpack_sum`` will be used, where ``XXX`` is source + archive path. This file must be kept in target's persistent storage. + +tar-strip + Tarball specific. See ``man tar`` for ``--strip-components``. + +tar-extra-args + Tarball sepcific. Append additional arguments to ``tar`` command. + See ``man tar`` for possible arguments. + + +OPTIONAL BOOLEAN PARAMETERS +--------------------------- +backup-destination + By default destination file will be overwritten. In case destination + is directory, files from archive will be added to or overwritten in + directory. This parameter moves existing destination to + ``XXX.cdist__unpack_backup_YYY``, where ``XXX`` is destination and + ``YYY`` current UNIX timestamp. + +preserve-archive + Don't delete archive after unpacking. + +onchange + Execute this command after unpack. + + +EXAMPLES +-------- + +.. code-block:: sh + + __directory /opt/cpma + + require='__directory/opt/cpma' \ + __download /opt/cpma/cnq3.zip \ + --url https://cdn.playmorepromode.com/files/cnq3/cnq3-1.51.zip \ + --sum md5:46da3021ca9eace277115ec9106c5b46 + + require='__download/opt/cpma/cnq3.zip' \ + __unpack /opt/cpma/cnq3.zip \ + --backup-destination \ + --preserve-archive \ + --destination /opt/cpma/server + + # example usecase for --tar-* args + __unpack /root/strelaysrv.tar.gz \ + --preserve-archive \ + --destination /usr/local/bin \ + --tar-strip 1 \ + --tar-extra-args '--wildcards "*/strelaysrv"' + + +AUTHORS +------- +Ander Punnar + + +COPYING +------- +Copyright \(C) 2020 Ander Punnar. 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/__unpack/manifest b/cdist/conf/type/__unpack/manifest new file mode 100755 index 00000000..6bdf5a10 --- /dev/null +++ b/cdist/conf/type/__unpack/manifest @@ -0,0 +1,41 @@ +#!/bin/sh -e + +os="$( cat "$__global/explorer/os" )" + +src="/$__object_id" + +case "$src" in + *.7z) + __package p7zip + ;; + *.bz2) + case "$os" in + freebsd) + # bzip2 is part of freebsd base system + ;; + *) + __package bzip2 + ;; + esac + ;; + *.lzma|*.xz|*.txz) + case "$os" in + debian|ubuntu|devuan) + __package xz-utils + ;; + alpine|centos) + __package xz + ;; + esac + ;; + *.rar) + case "$os" in + debian|ubuntu|devuan|alpine|freebsd) + __package unrar + ;; + esac + ;; + *.zip) + __package unzip + ;; +esac diff --git a/cdist/conf/type/__unpack/parameter/boolean b/cdist/conf/type/__unpack/parameter/boolean new file mode 100644 index 00000000..99dca934 --- /dev/null +++ b/cdist/conf/type/__unpack/parameter/boolean @@ -0,0 +1,2 @@ +backup-destination +preserve-archive diff --git a/cdist/conf/type/__unpack/parameter/optional b/cdist/conf/type/__unpack/parameter/optional new file mode 100644 index 00000000..d846ac75 --- /dev/null +++ b/cdist/conf/type/__unpack/parameter/optional @@ -0,0 +1,4 @@ +sum-file +tar-strip +tar-extra-args +onchange diff --git a/cdist/conf/type/__unpack/parameter/required b/cdist/conf/type/__unpack/parameter/required new file mode 100644 index 00000000..ac459b09 --- /dev/null +++ b/cdist/conf/type/__unpack/parameter/required @@ -0,0 +1 @@ +destination diff --git a/cdist/conf/type/__unpack/test/README b/cdist/conf/type/__unpack/test/README new file mode 100644 index 00000000..54f3972a --- /dev/null +++ b/cdist/conf/type/__unpack/test/README @@ -0,0 +1,3 @@ +./make-test-files.sh +./make-init-manifest.sh | cdist config -i - localhost +sudo find /tmp/cdist__unpack_test/ -type f -exec cat {} \; | sort diff --git a/cdist/conf/type/__unpack/test/make-init-manifest.sh b/cdist/conf/type/__unpack/test/make-init-manifest.sh new file mode 100755 index 00000000..404bc106 --- /dev/null +++ b/cdist/conf/type/__unpack/test/make-init-manifest.sh @@ -0,0 +1,22 @@ +#!/bin/sh -e + +p="$( pwd )" +d=/tmp/cdist__unpack_test + +echo 'export CDIST_ORDER_DEPENDENCY=1' + +echo "__directory $d" + +find "$p" -name 'test.*' -and -not -name '*.cdist__unpack_sum' \ + | sort \ + | while read -r l +do + n="$( basename "$l" )" + + printf '__unpack %s --destination %s/%s\n' \ + "$l" \ + "$d" \ + "$n" +done + +echo "__clean_path $p --pattern '.+/test\..+'" diff --git a/cdist/conf/type/__unpack/test/make-test-files.sh b/cdist/conf/type/__unpack/test/make-test-files.sh new file mode 100755 index 00000000..d18e9e9f --- /dev/null +++ b/cdist/conf/type/__unpack/test/make-test-files.sh @@ -0,0 +1,44 @@ +#!/bin/sh -ex + +echo test.7z > test +7z a test.7z test > /dev/null + +echo test.bz2 > test +bzip2 test + +echo test.gz > test +gzip test + +echo test.lzma > test +lzma test + +echo test.rar > test +rar a test.rar test > /dev/null + +echo test.tar.bz2 > test +tar cf test.tar test +bzip2 test.tar + +echo test.tar.xz > test +tar cf test.tar test +xz test.tar + +echo test.tgz > test +tar cf test.tar test +gzip test.tar +mv test.tar.gz test.tgz + +echo test.tar.gz > test +tar cf test.tar test +gzip test.tar + +echo test.tar > test +tar cf test.tar test + +echo test.xz > test +xz test + +echo test.zip > test +zip test.zip test > /dev/null + +rm test diff --git a/cdist/conf/type/__user/explorer/shadow b/cdist/conf/type/__user/explorer/shadow index 63d38f0d..32496ee7 100755 --- a/cdist/conf/type/__user/explorer/shadow +++ b/cdist/conf/type/__user/explorer/shadow @@ -23,18 +23,25 @@ name=$__object_id -case $("$__explorer/os") in - 'freebsd'|'netbsd'|'openbsd'|'alpine') +case $("${__explorer}/os") in + freebsd|netbsd) database='passwd' ;; - # Default to using shadow passwords + openbsd) + database='master.passwd' + ;; *) + # Default to using shadow passwords database='shadow' ;; esac -if command -v getent >/dev/null; then - getent "$database" "$name" || true -elif [ -f /etc/shadow ]; then - grep "^${name}:" /etc/shadow || true +if command -v getent >/dev/null 2>&1 +then + # shellcheck disable=SC2015 + getent "${database}" "${name}" 2>/dev/null && exit || true # fallback to file +fi +if test -n "${database}" -a -f "/etc/${database}" +then + grep -e "^${name}:" "/etc/${database}" || true # ignore failure fi diff --git a/cdist/conf/type/__user/gencode-remote b/cdist/conf/type/__user/gencode-remote index ee18c18f..41c3a57b 100755 --- a/cdist/conf/type/__user/gencode-remote +++ b/cdist/conf/type/__user/gencode-remote @@ -135,11 +135,19 @@ elif [ "$state" = "absent" ]; then if grep -q "^${name}:" "$__object/explorer/passwd"; then #user exists, but state != present, so delete it if [ -f "$__object/parameter/remove-home" ]; then - printf "userdel -r '%s' >/dev/null 2>&1\\n" "${name}" - echo "userdel -r" >> "$__messages_out" + if [ "$os" = "freebsd" ]; then + printf "pw userdel '%s' -r >/dev/null 2>&1\\n" "${name}" + else + printf "userdel -r '%s' >/dev/null 2>&1\\n" "${name}" + fi + echo "userdel -r" >> "$__messages_out" else - printf "userdel '%s' >/dev/null 2>&1\\n" "${name}" - echo "userdel" >> "$__messages_out" + if [ "$os" = "freebsd" ]; then + printf "pw userdel '%s' >/dev/null 2>&1\\n" "${name}" + else + printf "userdel '%s' >/dev/null 2>&1\\n" "${name}" + fi + echo "userdel" >> "$__messages_out" fi fi else diff --git a/cdist/conf/type/__user/manifest b/cdist/conf/type/__user/manifest index 8f10b38c..b9fad65b 100644 --- a/cdist/conf/type/__user/manifest +++ b/cdist/conf/type/__user/manifest @@ -1,6 +1,7 @@ #!/bin/sh -e # # 2019 Nico Schottelius (nico-cdist at schottelius.org) +# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch) # # This file is part of cdist. # @@ -17,16 +18,37 @@ # You should have received a copy of the GNU General Public License # along with cdist. If not, see . # -# # Manage users. +# -os=$(cat "$__global/explorer/os") - -case "$os" in - alpine) - __package shadow - ;; - *) - : - ;; +case $(cat "${__global}/explorer/os") +in + (alpine) + __package shadow + ;; + (openwrt) + case $(cat "${__object}/parameter/state") + in + (present) + if test -s "${__object}/explorer/passwd" + then + # NOTE: The package might not be required if no changes + # are required, but determining if changes are required is + # out of scope here, and 40k should be okay, I hope. + __package shadow-usermod + else + __package shadow-useradd + fi + ;; + (absent) + if test -s "${__object}/explorer/passwd" + then + __package shadow-userdel + fi + ;; + esac + ;; + (*) + : + ;; esac diff --git a/cdist/config.py b/cdist/config.py index 97cc1da6..e84f6f84 100644 --- a/cdist/config.py +++ b/cdist/config.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # 2010-2015 Nico Schottelius (nico-cdist at schottelius.org) @@ -29,18 +28,20 @@ import time import itertools import tempfile import multiprocessing -from cdist.mputil import mp_pool_run, mp_sig_handler import atexit import shutil import socket + +from cdist.mputil import mp_pool_run, mp_sig_handler +from cdist import core, inventory +from cdist.util.remoteutil import inspect_ssh_mux_opts + import cdist import cdist.hostsource import cdist.exec.local import cdist.exec.remote import cdist.util.ipaddr as ipaddr import cdist.configuration -from cdist import core, inventory -from cdist.util.remoteutil import inspect_ssh_mux_opts def graph_check_cycle(graph): @@ -70,7 +71,7 @@ def _graph_dfs_cycle(graph, node, path): return False -class Config(object): +class Config: """Cdist main class to hold arbitrary data""" # list of paths (files and/or directories) that will be removed on finish @@ -174,9 +175,11 @@ class Config(object): raise cdist.Error(("Cannot read both, manifest and host file, " "from stdin")) - # if no host source is specified then read hosts from stdin if not (args.hostfile or args.host): - args.hostfile = '-' + if args.tag or args.all_tagged_hosts: + raise cdist.Error(("Target host tag(s) missing")) + else: + raise cdist.Error(("Target host(s) missing")) if args.manifest == '-': # read initial manifest from stdin @@ -195,7 +198,6 @@ class Config(object): @classmethod def commandline(cls, args): """Configure remote system""" - if (args.parallel and args.parallel != 1) or args.jobs: if args.timestamp: cdist.log.setupTimestampingParallelLogging() @@ -203,6 +205,7 @@ class Config(object): cdist.log.setupParallelLogging() elif args.timestamp: cdist.log.setupTimestampingLogging() + log = logging.getLogger("config") # No new child process if only one host at a time. @@ -381,10 +384,16 @@ class Config(object): If operating in parallel then return tuple (host, True|False, ) so that main process knows for which host function was successful. """ - log = logging.getLogger(host) try: + if args.log_server: + # Start a log server so that nested `cdist config` runs + # have a place to send their logs to. + log_server_socket_dir = tempfile.mkdtemp() + cls._register_path_for_removal(log_server_socket_dir) + cdist.log.setupLogServer(log_server_socket_dir, log) + remote_exec, remote_copy, cleanup_cmd = cls._resolve_remote_cmds( args) log.debug("remote_exec for host \"{}\": {}".format( diff --git a/cdist/configuration.py b/cdist/configuration.py index 1011a382..4ba43a7a 100644 --- a/cdist/configuration.py +++ b/cdist/configuration.py @@ -27,6 +27,7 @@ import cdist.argparse import re import multiprocessing import logging +import sys class Singleton(type): @@ -246,9 +247,33 @@ class LogLevelOption(OptionBase): return VerbosityOption().translate(val) +class ColoredOutputOption(BooleanOption): + CHOICES = ('always', 'never', 'auto') + DEFAULT = 'auto' + + def get_converter(self): + return self.translate + + @staticmethod + def translate(val): + if isinstance(val, bool): + return val + elif val == 'always': + return True + elif val == 'never': + return False + elif val == 'auto': + return 'NO_COLOR' not in os.environ and sys.stdout.isatty() + + +ColoredOutputOption.DEFAULT = ColoredOutputOption.translate( + ColoredOutputOption.DEFAULT) + + _ARG_OPTION_MAPPING = { 'beta': 'beta', 'cache_path_pattern': 'cache_path_pattern', + 'colored_output': 'colored_output', 'conf_dir': 'conf_dir', 'manifest': 'init_manifest', 'out_path': 'out_path', @@ -294,6 +319,7 @@ class Configuration(metaclass=Singleton): 'remote_shell': StringOption('remote_shell'), 'cache_path_pattern': StringOption('cache_path_pattern'), 'conf_dir': ConfDirOption(), + 'colored_output': ColoredOutputOption('colored_output'), 'init_manifest': StringOption('init_manifest'), 'out_path': StringOption('out_path'), 'remote_out_path': StringOption('remote_out_path'), @@ -319,6 +345,7 @@ class Configuration(metaclass=Singleton): 'CDIST_REMOTE_COPY': 'remote_copy', 'CDIST_INVENTORY_DIR': 'inventory_dir', 'CDIST_CACHE_PATH_PATTERN': 'cache_path_pattern', + 'CDIST_COLORED_OUTPUT': 'colored_output', '__cdist_log_level': 'verbosity', } ENV_VAR_BOOLEAN_OPTIONS = ('CDIST_BETA', ) @@ -327,11 +354,10 @@ class Configuration(metaclass=Singleton): } ARG_OPTION_MAPPING = _ARG_OPTION_MAPPING - ADJUST_ARG_OPTION_MAPPING = { - _ARG_OPTION_MAPPING[key]: key for key in _ARG_OPTION_MAPPING - } + ADJUST_ARG_OPTION_MAPPING = {v: k for k, v in _ARG_OPTION_MAPPING.items()} REQUIRED_DEFAULT_CONFIG_VALUES = { 'GLOBAL': { + 'colored_output': 'auto', 'verbosity': 0, }, } @@ -484,8 +510,7 @@ class Configuration(metaclass=Singleton): newconfig = self._read_config_file(config_file) self._update_config_dict(config, newconfig) # command line config file - if (self.args and 'config_file' in self.args and - self.args['config_file']): + if (self.args and self.args.get('config_file', None)): newconfig = self._read_config_file(self.args['config_file']) self._update_config_dict(config, newconfig) # command line diff --git a/cdist/core/cdist_object.py b/cdist/core/cdist_object.py index 114a47e0..51d61e04 100644 --- a/cdist/core/cdist_object.py +++ b/cdist/core/cdist_object.py @@ -47,7 +47,7 @@ class MissingObjectIdError(cdist.Error): return '%s' % (self.message) -class CdistObject(object): +class CdistObject: """Represents a cdist object. All interaction with objects in cdist should be done through this class. diff --git a/cdist/core/cdist_type.py b/cdist/core/cdist_type.py index 4500f50d..c0329c8a 100644 --- a/cdist/core/cdist_type.py +++ b/cdist/core/cdist_type.py @@ -38,7 +38,7 @@ class InvalidTypeError(cdist.Error): self.type_path, self.type_absolute_path, self.source_path) -class CdistType(object): +class CdistType: """Represents a cdist type. All interaction with types in cdist should be done through this class. diff --git a/cdist/core/code.py b/cdist/core/code.py index 1550880a..1e9b4f80 100644 --- a/cdist/core/code.py +++ b/cdist/core/code.py @@ -92,7 +92,7 @@ code-remote ''' -class Code(object): +class Code: """Generates and executes cdist code scripts. """ @@ -116,6 +116,10 @@ class Code(object): if dry_run: self.env['__cdist_dry_run'] = '1' + if '__cdist_log_server_socket_export' in os.environ: + self.env['__cdist_log_server_socket'] = os.environ[ + '__cdist_log_server_socket_export'] + def _run_gencode(self, cdist_object, which): cdist_type = cdist_object.cdist_type script = os.path.join(self.local.type_path, diff --git a/cdist/core/explorer.py b/cdist/core/explorer.py index 353d7681..a3baa959 100644 --- a/cdist/core/explorer.py +++ b/cdist/core/explorer.py @@ -63,7 +63,7 @@ type explorer is: ''' -class Explorer(object): +class Explorer: """Executes cdist explorers. """ diff --git a/cdist/core/manifest.py b/cdist/core/manifest.py index 8aeaf860..390340d4 100644 --- a/cdist/core/manifest.py +++ b/cdist/core/manifest.py @@ -92,7 +92,7 @@ class NoInitialManifestError(cdist.Error): return repr(self.message) -class Manifest(object): +class Manifest: """Executes cdist manifests. """ @@ -119,6 +119,8 @@ class Manifest(object): '__cdist_log_level': util.log_level_env_var_val(self.log), '__cdist_log_level_name': util.log_level_name_env_var_val( self.log), + '__cdist_colored_log': str( + cdist.log.CdistFormatter.USE_COLORS).lower(), } if dry_run: diff --git a/cdist/emulator.py b/cdist/emulator.py index 4800e2a3..60b94880 100644 --- a/cdist/emulator.py +++ b/cdist/emulator.py @@ -25,6 +25,7 @@ import argparse import logging import os import sys +import re import cdist from cdist import core @@ -56,7 +57,7 @@ class DefaultList(list): return cls(initial.split('\n')) -class Emulator(object): +class Emulator: def __init__(self, argv, stdin=sys.stdin.buffer, env=os.environ): self.argv = argv self.stdin = stdin @@ -91,10 +92,6 @@ class Emulator(object): self.type_name = os.path.basename(argv[0]) self.cdist_type = core.CdistType(self.type_base_path, self.type_name) - # If set then object alreay exists and this var holds existing - # requirements. - self._existing_reqs = None - self.__init_log() def run(self): @@ -129,6 +126,9 @@ class Emulator(object): # if invalid __cdist_log_level value logging.root.setLevel(logging.WARNING) + colored_log = self.env.get('__cdist_colored_log', 'false') + cdist.log.CdistFormatter.USE_COLORS = colored_log == 'true' + self.log = logging.getLogger(self.target_host[0]) def commandline(self): @@ -227,9 +227,6 @@ class Emulator(object): self.parameters[key] = value if self.cdist_object.exists and 'CDIST_OVERRIDE' not in self.env: - # Make existing requirements a set so that we can compare it - # later with new requirements. - self._existing_reqs = set(self.cdist_object.requirements) obj_params = self._object_params_in_context() if obj_params != self.parameters: errmsg = ("Object %s already exists with conflicting " @@ -248,23 +245,26 @@ class Emulator(object): else: self.cdist_object.create() self.cdist_object.parameters = self.parameters - # record the created object in typeorder file - with open(self.typeorder_path, 'a') as typeorderfile: - print(self.cdist_object.name, file=typeorderfile) - # record the created object in parent object typeorder file - __object_name = self.env.get('__object_name', None) - depname = self.cdist_object.name - if __object_name: - parent = self.cdist_object.object_from_name(__object_name) - parent.typeorder.append(self.cdist_object.name) - if self._order_dep_on(): - self.log.trace(('[ORDER_DEP] Adding %s to typeorder dep' - ' for %s'), depname, parent.name) - parent.typeorder_dep.append(depname) - elif self._order_dep_on(): - self.log.trace('[ORDER_DEP] Adding %s to global typeorder dep', - depname) - self._add_typeorder_dep(depname) + # Do the following recording even if object exists, but with + # different requirements. + + # record the created object in typeorder file + with open(self.typeorder_path, 'a') as typeorderfile: + print(self.cdist_object.name, file=typeorderfile) + # record the created object in parent object typeorder file + __object_name = self.env.get('__object_name', None) + depname = self.cdist_object.name + if __object_name: + parent = self.cdist_object.object_from_name(__object_name) + parent.typeorder.append(self.cdist_object.name) + if self._order_dep_on(): + self.log.trace(('[ORDER_DEP] Adding %s to typeorder dep' + ' for %s'), depname, parent.name) + parent.typeorder_dep.append(depname) + elif self._order_dep_on(): + self.log.trace('[ORDER_DEP] Adding %s to global typeorder dep', + depname) + self._add_typeorder_dep(depname) # Record / Append source self.cdist_object.source.append(self.object_source) @@ -319,8 +319,6 @@ class Emulator(object): # This ensures pattern matching is done against sanitised list self.cdist_object.requirements.append(cdist_object.name) - return cdist_object.name - def _order_dep_on(self): return os.path.exists(self.order_dep_state_path) @@ -389,31 +387,17 @@ class Emulator(object): # so do not set a requirement pass - reqs = set() if "require" in self.env: requirements = self.env['require'] self.log.debug("reqs = " + requirements) - for requirement in requirements.split(" "): + for requirement in self._parse_require(requirements): # Ignore empty fields - probably the only field anyway if len(requirement) == 0: continue - object_name = self.record_requirement(requirement) - reqs.add(object_name) - if self._existing_reqs is not None: - # If object exists then compare existing and new requirements. - if self._existing_reqs != reqs: - warnmsg = ("Object {} already exists with requirements:\n" - "{}: {}\n" - "{}: {}\n" - "Dependency resolver could not handle dependencies " - "as expected.".format( - self.cdist_object.name, - " ".join(self.cdist_object.source), - self._existing_reqs, - self.object_source, - reqs - )) - self.log.warning(warnmsg) + self.record_requirement(requirement) + + def _parse_require(self, require): + return re.split(r'[ \t\n]+', require) def record_auto_requirements(self): """An object shall automatically depend on all objects that it diff --git a/cdist/exec/local.py b/cdist/exec/local.py index ad6c6e36..e0aab190 100644 --- a/cdist/exec/local.py +++ b/cdist/exec/local.py @@ -39,7 +39,7 @@ import cdist.exec.util as util CONF_SUBDIRS_LINKED = ["explorer", "files", "manifest", "type", ] -class Local(object): +class Local: """Execute commands locally. All interaction with the local side should be done through this class. diff --git a/cdist/exec/remote.py b/cdist/exec/remote.py index e0ef66ec..e5af2f34 100644 --- a/cdist/exec/remote.py +++ b/cdist/exec/remote.py @@ -49,7 +49,7 @@ class DecodeError(cdist.Error): return "Cannot decode output of " + " ".join(self.command) -class Remote(object): +class Remote: """Execute commands remotely. All interaction with the remote side should be done through this class. @@ -280,9 +280,6 @@ class Remote(object): assert isinstance(command, (list, tuple)), ( "list or tuple argument expected, got: %s" % command) - if return_output and stdout is not subprocess.PIPE: - self.log.debug("return_output is True, ignoring stdout") - close_stdout = False close_stderr = False if self.save_output_streams: diff --git a/cdist/flock.py b/cdist/flock.py index d8bac916..3afacfd9 100644 --- a/cdist/flock.py +++ b/cdist/flock.py @@ -22,6 +22,7 @@ import fcntl import logging import os +import cdist.log log = logging.getLogger('cdist-flock') diff --git a/cdist/hostsource.py b/cdist/hostsource.py index a7b8f0b4..5f927b36 100644 --- a/cdist/hostsource.py +++ b/cdist/hostsource.py @@ -41,7 +41,7 @@ def hostfile_process_line(line, strip_func=str.strip): return None -class HostSource(object): +class HostSource: """ Host source object. Source can be a sequence or filename (stdin if \'-\'). diff --git a/cdist/info.py b/cdist/info.py index b896a3d1..20cb099a 100644 --- a/cdist/info.py +++ b/cdist/info.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # 2019-2020 Darko Poljak (darko.poljak at gmail.com) @@ -29,8 +28,7 @@ import glob import fnmatch -class Info(object): - +class Info: def __init__(self, conf_dirs, args): self.conf_dirs = conf_dirs self.all = args.all diff --git a/cdist/install.py b/cdist/install.py index b88ad016..7c894fe5 100644 --- a/cdist/install.py +++ b/cdist/install.py @@ -1,7 +1,6 @@ -#!/usr/bin/env python3 # -*- coding: utf-8 -*- # -# 2013 Steven Armstrong (steven-cdist at armstrong.cc) +# 2013-2019 Steven Armstrong (steven-cdist at armstrong.cc) # # This file is part of cdist. # @@ -22,9 +21,21 @@ import cdist.config import cdist.core +import cdist.log class Install(cdist.config.Config): + + @classmethod + def onehost(cls, host, host_tags, host_base_path, host_dir_name, args, + parallel, configuration, remove_remote_files_dirs=False): + # Always start log server during cdist install so that nested + # `cdist config` runs have a place to send their logs to. + args.log_server = True + + super().onehost(host, host_tags, host_base_path, host_dir_name, args, + parallel, configuration, remove_remote_files_dirs) + def object_list(self): """Short name for object list retrieval. In install mode, we only care about install objects. diff --git a/cdist/integration.py b/cdist/integration.py index ee742cc5..03e4167d 100644 --- a/cdist/integration.py +++ b/cdist/integration.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # 2017 Darko Poljak (darko.poljak at gmail.com) diff --git a/cdist/inventory.py b/cdist/inventory.py index 138a2034..0387f326 100644 --- a/cdist/inventory.py +++ b/cdist/inventory.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # 2016 Darko Poljak (darko.poljak at gmail.com) @@ -80,7 +79,7 @@ def rstrip_nl(s): return str.rstrip(s, "\n") -class Inventory(object): +class Inventory: """Inventory main class""" def __init__(self, db_basedir=dist_inventory_db, configuration=None): @@ -300,7 +299,7 @@ class InventoryHost(Inventory): self.all = all if not self.hosts and not self.hostfile: - self.hostfile = "-" + raise cdist.Error("Host(s) missing") def _new_hostpath(self, hostpath): # create empty file @@ -356,7 +355,7 @@ class InventoryTag(Inventory): else: self.allhosts = False if not self.tags and not self.tagfile: - self.tagfile = "-" + raise cdist.Error("Tag(s) missing") if self.hostfile == "-" and self.tagfile == "-": raise cdist.Error("Cannot read both, hosts and tags, from stdin") diff --git a/cdist/log.py b/cdist/log.py index 5d431130..113f3b4c 100644 --- a/cdist/log.py +++ b/cdist/log.py @@ -1,7 +1,7 @@ -#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # 2010-2013 Nico Schottelius (nico-cdist at schottelius.org) +# 2019-2020 Steven Armstrong # # This file is part of cdist. # @@ -20,9 +20,16 @@ # # -import logging -import sys import datetime +import logging +import logging.handlers +import sys +import os +import asyncio +import contextlib +import pickle +import struct +import threading # Define additional cdist logging levels. @@ -50,9 +57,32 @@ def _trace(msg, *args, **kwargs): logging.trace = _trace -class DefaultLog(logging.Logger): +class CdistFormatter(logging.Formatter): + USE_COLORS = False + RESET = '\033[0m' + COLOR_MAP = { + 'ERROR': '\033[0;31m', + 'WARNING': '\033[0;33m', + 'INFO': '\033[0;94m', + 'VERBOSE': '\033[0;34m', + 'DEBUG': '\033[0;90m', + 'TRACE': '\033[0;37m', + } - FORMAT = '%(levelname)s: %(message)s' + def __init__(self, fmt): + super().__init__(fmt=fmt) + + def format(self, record): + msg = super().format(record) + if self.USE_COLORS: + color = self.COLOR_MAP.get(record.levelname) + if color: + msg = color + msg + self.RESET + return msg + + +class DefaultLog(logging.Logger): + FORMAT = '%(levelname)s: %(name)s: %(message)s' class StdoutFilter(logging.Filter): def filter(self, rec): @@ -64,30 +94,28 @@ class DefaultLog(logging.Logger): def __init__(self, name): super().__init__(name) + self.propagate = False - formatter = logging.Formatter(self.FORMAT) + if '__cdist_log_server_socket' in os.environ: + log_server_socket = os.environ['__cdist_log_server_socket'] + socket_handler = logging.handlers.SocketHandler(log_server_socket, + None) + self.addHandler(socket_handler) + else: + formatter = CdistFormatter(self.FORMAT) - self.addFilter(self) + stdout_handler = logging.StreamHandler(sys.stdout) + stdout_handler.addFilter(self.StdoutFilter()) + stdout_handler.setLevel(logging.TRACE) + stdout_handler.setFormatter(formatter) - stdout_handler = logging.StreamHandler(sys.stdout) - stdout_handler.addFilter(self.StdoutFilter()) - stdout_handler.setLevel(logging.TRACE) - stdout_handler.setFormatter(formatter) + stderr_handler = logging.StreamHandler(sys.stderr) + stderr_handler.addFilter(self.StderrFilter()) + stderr_handler.setLevel(logging.ERROR) + stderr_handler.setFormatter(formatter) - stderr_handler = logging.StreamHandler(sys.stderr) - stderr_handler.addFilter(self.StderrFilter()) - stderr_handler.setLevel(logging.ERROR) - stderr_handler.setFormatter(formatter) - - self.addHandler(stdout_handler) - self.addHandler(stderr_handler) - - def filter(self, record): - """Prefix messages with logger name""" - - record.msg = self.name + ": " + str(record.msg) - - return True + self.addHandler(stdout_handler) + self.addHandler(stderr_handler) def verbose(self, msg, *args, **kwargs): self.log(logging.VERBOSE, msg, *args, **kwargs) @@ -110,7 +138,7 @@ class TimestampingLog(DefaultLog): class ParallelLog(DefaultLog): - FORMAT = '%(levelname)s: [%(process)d]: %(message)s' + FORMAT = '%(levelname)s: [%(process)d]: %(name)s: %(message)s' class TimestampingParallelLog(TimestampingLog, ParallelLog): @@ -137,4 +165,42 @@ def setupParallelLogging(): logging.setLoggerClass(ParallelLog) +async def handle_log_client(reader, writer): + while True: + chunk = await reader.read(4) + if len(chunk) < 4: + return + + data_size = struct.unpack('>L', chunk)[0] + data = await reader.read(data_size) + + obj = pickle.loads(data) + record = logging.makeLogRecord(obj) + logger = logging.getLogger(record.name) + logger.handle(record) + + +def run_log_server(server_address): + # Get a new loop inside the current thread to run the log server. + loop = asyncio.new_event_loop() + loop.create_task(asyncio.start_unix_server(handle_log_client, + server_address)) + loop.run_forever() + + +def setupLogServer(socket_dir, log=logging.getLogger(__name__)): + """Run a asyncio based unix socket log server in a background thread. + """ + log_server_socket = os.path.join(socket_dir, 'log-server') + log.debug('Starting logging server on: %s', log_server_socket) + os.environ['__cdist_log_server_socket_export'] = log_server_socket + with contextlib.suppress(FileNotFoundError): + os.remove(log_server_socket) + t = threading.Thread(target=run_log_server, args=(log_server_socket,)) + # Deamonizing the thread means we don't have to care about stoping it. + # It will die together with the main process. + t.daemon = True + t.start() + + setupDefaultLogging() diff --git a/cdist/message.py b/cdist/message.py index 450fc3c3..ffa8c2bb 100644 --- a/cdist/message.py +++ b/cdist/message.py @@ -27,7 +27,7 @@ import tempfile log = logging.getLogger(__name__) -class Message(object): +class Message: """Support messaging between types """ diff --git a/cdist/preos.py b/cdist/preos.py index bf2a8e60..f8a5dd67 100644 --- a/cdist/preos.py +++ b/cdist/preos.py @@ -78,7 +78,7 @@ def get_available_preoses_string(cls): return "Available PreOS-es:\n{}".format("\n".join(preoses)) -class PreOS(object): +class PreOS: preoses = None @classmethod diff --git a/cdist/preos/debootstrap/debootstrap.py b/cdist/preos/debootstrap/debootstrap.py index f53dd4a7..a20cdb9c 100644 --- a/cdist/preos/debootstrap/debootstrap.py +++ b/cdist/preos/debootstrap/debootstrap.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # 2016 Darko Poljak (darko.poljak at ungleich.ch) @@ -31,7 +30,7 @@ import os import subprocess -class Debian(object): +class Debian: _preos_name = 'debian' _cdist_preos = True diff --git a/cdist/shell.py b/cdist/shell.py index 60b6a9f0..04a68937 100644 --- a/cdist/shell.py +++ b/cdist/shell.py @@ -32,10 +32,8 @@ import cdist.config log = logging.getLogger(__name__) -class Shell(object): - +class Shell: def __init__(self, shell=None): - self.shell = shell self.target_host = ( diff --git a/cdist/sphinxext/manpage.py b/cdist/sphinxext/manpage.py index 135fe22e..1f8ac4f6 100644 --- a/cdist/sphinxext/manpage.py +++ b/cdist/sphinxext/manpage.py @@ -7,6 +7,7 @@ from docutils.io import FileOutput from os import path from sphinx.util.nodes import inline_all_toctrees from sphinx import addnodes +from sphinx.util import logging """ Extension based on sphinx builtin manpage. @@ -15,6 +16,9 @@ from sphinx import addnodes """ +logger = logging.getLogger(__name__) + + class ManualPageTranslator(sphinx.writers.manpage.ManualPageTranslator): def header(self): @@ -28,7 +32,7 @@ class ManualPageWriter(sphinx.writers.manpage.ManualPageWriter): def __init__(self, builder): super().__init__(builder) self.translator_class = ( - self.builder.translator_class or ManualPageTranslator) + self.builder.get_translator_class() or ManualPageTranslator) class ManualPageBuilder(sphinx.builders.manpage.ManualPageBuilder): @@ -43,7 +47,7 @@ class ManualPageBuilder(sphinx.builders.manpage.ManualPageBuilder): components=(docwriter,), read_config_files=True).get_default_values() - self.info(bold('writing... '), nonl=True) + logger.info(bold('writing... '), nonl=True) for info in self.config.man_pages: docname, name, description, authors, section = info @@ -54,7 +58,7 @@ class ManualPageBuilder(sphinx.builders.manpage.ManualPageBuilder): authors = [] targetname = '%s.%s' % (name, section) - self.info(darkgreen(targetname) + ' { ', nonl=True) + logger.info(darkgreen(targetname) + ' { ', nonl=True) destination = FileOutput( destination_path=path.join(self.outdir, targetname), encoding='utf-8') @@ -63,7 +67,7 @@ class ManualPageBuilder(sphinx.builders.manpage.ManualPageBuilder): docnames = set() largetree = inline_all_toctrees(self, docnames, docname, tree, darkgreen, [docname]) - self.info('} ', nonl=True) + logger.info('} ', nonl=True) self.env.resolve_references(largetree, docname, self) # remove pending_xref nodes for pendingnode in largetree.traverse(addnodes.pending_xref): @@ -76,7 +80,7 @@ class ManualPageBuilder(sphinx.builders.manpage.ManualPageBuilder): largetree.settings.section = section docwriter.write(largetree, destination) - self.info() + logger.info("") def setup(app): diff --git a/cdist/test/config/__init__.py b/cdist/test/config/__init__.py index 499593e3..0ed614b1 100644 --- a/cdist/test/config/__init__.py +++ b/cdist/test/config/__init__.py @@ -44,7 +44,7 @@ expected_object_names = sorted([ '__third/moon']) -class CdistObjectErrorContext(object): +class CdistObjectErrorContext: def __init__(self, original_error): self.original_error = original_error diff --git a/cdist/test/configuration/__init__.py b/cdist/test/configuration/__init__.py index 182868a6..3fd24ca5 100644 --- a/cdist/test/configuration/__init__.py +++ b/cdist/test/configuration/__init__.py @@ -28,10 +28,12 @@ import argparse from cdist import test import cdist.argparse as cap import logging +import sys my_dir = op.abspath(op.dirname(__file__)) fixtures = op.join(my_dir, 'fixtures') interpolation_config_file = op.join(fixtures, "interpolation-test.cfg") +colored_output_default = 'auto' def newConfigParser(): @@ -153,6 +155,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/bin/sh', 'inventory_dir': '', 'cache_path_pattern': '', + 'colored_output': colored_output_default, 'conf_dir': '', 'init_manifest': '', 'out_path': '', @@ -184,6 +187,8 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/bin/sh', 'inventory_dir': None, 'cache_path_pattern': None, + 'colored_output': cc.ColoredOutputOption.translate( + colored_output_default), 'conf_dir': None, 'init_manifest': None, 'out_path': None, @@ -390,6 +395,7 @@ class ConfigurationTestCase(test.CdistTestCase): args = argparse.Namespace() expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'verbosity': 0, }, } @@ -440,6 +446,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/bin/sh', 'inventory_dir': None, 'cache_path_pattern': None, + 'colored_output': colored_output_default, 'conf_dir': None, 'init_manifest': None, 'out_path': None, @@ -515,6 +522,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/usr/bin/sh', 'inventory_dir': '/var/db/cdist/inventory', 'cache_path_pattern': None, + 'colored_output': colored_output_default, 'conf_dir': ['/opt/cdist', ], 'init_manifest': None, 'out_path': None, @@ -556,6 +564,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/bin/sh', 'inventory_dir': '', 'cache_path_pattern': '', + 'colored_output': colored_output_default, 'conf_dir': '', 'init_manifest': '', 'out_path': '', @@ -579,6 +588,8 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/usr/bin/sh', 'inventory_dir': None, 'cache_path_pattern': None, + 'colored_output': cc.ColoredOutputOption.translate( + colored_output_default), 'conf_dir': [ '/opt/cdist/conf', '/usr/local/share/cdist/conf', @@ -623,6 +634,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/bin/sh', 'inventory_dir': '', 'cache_path_pattern': '', + 'colored_output': colored_output_default, 'conf_dir': '', 'init_manifest': '', 'out_path': '', @@ -645,6 +657,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'local_shell': '/usr/bin/sh', 'remote_shell': '/usr/bin/sh', 'inventory_dir': '/var/db/cdist/inventory', + 'colored_output': colored_output_default, 'conf_dir': '/opt/cdist', 'remote_copy': 'myscp', 'remote_exec': 'myexec', @@ -663,6 +676,8 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/usr/bin/sh', 'inventory_dir': '/var/db/cdist/inventory', 'cache_path_pattern': None, + 'colored_output': cc.ColoredOutputOption.translate( + colored_output_default), 'conf_dir': [ '/opt/cdist/conf', '/usr/local/share/cdist/conf', @@ -694,6 +709,7 @@ class ConfigurationTestCase(test.CdistTestCase): } expected_config = { 'GLOBAL': { + 'colored_output': colored_output_default, 'verbosity': 0, }, } @@ -767,6 +783,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/usr/bin/sh', 'inventory_dir': '/opt/sysadmin/cdist/inventory', 'cache_path_pattern': None, + 'colored_output': colored_output_default, 'conf_dir': [ '/opt/cdist/conf', '/usr/local/share/cdist/conf', @@ -865,6 +882,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/usr/bin/sh', 'inventory_dir': '/var/db/cdist/inventory', 'cache_path_pattern': None, + 'colored_output': colored_output_default, 'conf_dir': [ '/opt/conf/cdist', ], @@ -964,6 +982,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/usr/bin/sh', 'inventory_dir': '/var/db/cdist/inventory', 'cache_path_pattern': None, + 'colored_output': colored_output_default, 'conf_dir': [ '/opt/conf/cdist', ], @@ -1063,6 +1082,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'remote_shell': '/usr/bin/sh', 'inventory_dir': '/var/db/cdist/inventory', 'cache_path_pattern': None, + 'colored_output': colored_output_default, 'conf_dir': [ '/opt/conf/cdist', ], @@ -1095,6 +1115,7 @@ class ConfigurationTestCase(test.CdistTestCase): 'beta': True, 'inventory_dir': '/var/db/cdist/inventory', 'cache_path_pattern': None, + 'colored_output': colored_output_default, 'conf_dir': [ '/opt/conf/cdist', ], @@ -1125,6 +1146,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { 'inventory_dir': None, + 'colored_output': colored_output_default, 'conf_dir': None, 'verbosity': 0, }, @@ -1148,6 +1170,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'verbosity': cap.VERBOSE_DEBUG, }, } @@ -1185,6 +1208,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'save_output_streams': True, 'verbosity': 0, }, @@ -1213,6 +1237,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'save_output_streams': False, 'verbosity': 0, }, @@ -1241,6 +1266,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'save_output_streams': False, 'verbosity': 0, }, @@ -1269,6 +1295,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'save_output_streams': False, 'verbosity': 0, }, @@ -1308,6 +1335,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'timestamp': True, 'verbosity': 0, }, @@ -1336,6 +1364,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'timestamp': True, 'verbosity': 0, }, @@ -1364,6 +1393,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'timestamp': False, 'verbosity': 0, }, @@ -1392,6 +1422,7 @@ class ConfigurationTestCase(test.CdistTestCase): expected_config_dict = { 'GLOBAL': { + 'colored_output': colored_output_default, 'timestamp': False, 'verbosity': 0, }, diff --git a/cdist/test/emulator/__init__.py b/cdist/test/emulator/__init__.py index e375676c..befd7b57 100644 --- a/cdist/test/emulator/__init__.py +++ b/cdist/test/emulator/__init__.py @@ -685,6 +685,16 @@ class EmulatorAlreadyExistingRequirementsWarnTestCase(test.CdistTestCase): self.env['require'] = '__directory/spam' emu = emulator.Emulator(argv, env=self.env) + def test_parse_require(self): + require = " \t \n \t\t\n\t\na\tb\nc d \te\t\nf\ng\t " + expected = ['', 'a', 'b', 'c', 'd', 'e', 'f', 'g', '', ] + + argv = ['__directory', 'spam'] + emu = emulator.Emulator(argv, env=self.env) + requirements = emu._parse_require(require) + + self.assertEqual(expected, requirements) + if __name__ == '__main__': import unittest diff --git a/cdist/test/inventory/__init__.py b/cdist/test/inventory/__init__.py index 287c855c..a8cd8bf8 100644 --- a/cdist/test/inventory/__init__.py +++ b/cdist/test/inventory/__init__.py @@ -307,11 +307,10 @@ class InventoryTestCase(test.CdistTestCase): raise e # InventoryTag + @unittest.expectedFailure def test_inventory_tag_init(self): invTag = inventory.InventoryTag(db_basedir=inventory_dir, action="add") - self.assertTrue(invTag.allhosts) - self.assertEqual(invTag.tagfile, "-") def test_inventory_tag_stdin_multiple_hosts(self): try: diff --git a/cdist/util/fsproperty.py b/cdist/util/fsproperty.py index 5a27c9d7..1d76fd76 100644 --- a/cdist/util/fsproperty.py +++ b/cdist/util/fsproperty.py @@ -177,7 +177,7 @@ class DirectoryDict(collections.MutableMapping): raise cdist.Error(str(e)) -class FileBasedProperty(object): +class FileBasedProperty: attribute_class = None def __init__(self, path): @@ -189,7 +189,7 @@ class FileBasedProperty(object): Usage with a sublcass: - class Foo(object): + class Foo: # note that the actual DirectoryDict is stored as __parameters # on the instance parameters = DirectoryDictProperty( diff --git a/cdist/util/ipaddr.py b/cdist/util/ipaddr.py index 9b730225..95ca74ee 100644 --- a/cdist/util/ipaddr.py +++ b/cdist/util/ipaddr.py @@ -45,8 +45,6 @@ def resolve_target_host_name(host, family=0): log.debug("derived host_name for host \"{}\": {}".format( host, host_name)) except (socket.gaierror, socket.herror) as e: - log.warning("Could not derive host_name for {}" - ", $host_name will be empty. Error is: {}".format(host, e)) # in case of error provide empty value host_name = '' return host_name @@ -59,8 +57,6 @@ def resolve_target_fqdn(host): log.debug("derived host_fqdn for host \"{}\": {}".format( host, host_fqdn)) except socket.herror as e: - log.warning("Could not derive host_fqdn for {}" - ", $host_fqdn will be empty. Error is: {}".format(host, e)) # in case of error provide empty value host_fqdn = '' return host_fqdn diff --git a/configuration/cdist.cfg.skeleton b/configuration/cdist.cfg.skeleton index 22c1ccaf..14861592 100644 --- a/configuration/cdist.cfg.skeleton +++ b/configuration/cdist.cfg.skeleton @@ -13,17 +13,28 @@ # Specify cache path pattern. # cache_path_pattern = %h # +# colored_output +# Colorize cdist's output. If enabled, cdist will use different colors for +# different log levels. +# Recognized values are 'always', 'never', and 'auto'. +# If the value is 'auto', colors are enabled if stdout is a TTY unless +# the NO_COLOR (https://no-color.org/) environment variable is defined. +# colored_output = auto +# # conf_dir # List of configuration directories separated with the character conventionally # used by the operating system to separate search path components (as in PATH), # such as ':' for POSIX or ';' for Windows. # If also specified at command line then values from command line are # appended to this value. +# Notice that this works in a "last one wins" fashion, so if a type is redefined +# in multiple conf_dirs, the last one in which it is defined will be used. +# Consider using a unique prefix for your own roles if this can be an issue. # conf_dir = : # # init_manifest # Specify default initial manifest. -# init_mainfest = +# init_manifest = # # inventory_dir # Specify inventory directory. @@ -42,7 +53,7 @@ # # out_path # Directory to save cdist output in. -# out_path = +# out_path = # # parallel # Process hosts in parallel. If -1 then the default, number of CPU's in @@ -68,6 +79,6 @@ # remote_shell = /bin/sh # # verbosity -# Set verbosity level. Valid values are: +# Set verbosity level. Valid values are: # ERROR, WARNING, INFO, VERBOSE, DEBUG, TRACE and OFF. # verbosity = INFO diff --git a/docs/changelog b/docs/changelog index 7cacf3cf..8d380524 100644 --- a/docs/changelog +++ b/docs/changelog @@ -2,6 +2,67 @@ Changelog --------- next: + * Core: Clarify stdin input (Darko Poljak) + * Type __package_pip: Detect pip binary (Ander Punnar) + * Documentation: Add custom remote copy/exec examples (Darko Poljak) + +6.8.0: 2020-09-11 + * Type __locale_system: Fix for debian and ubuntu (Ander Punnar) + * Type __unpack: Add --tar-extra-args parameter (Ander Punnar) + * Explorer os: Fix OS detection for openSUSE (Dennis Camera) + * Type __filesystem: Support Alpine Linux (Joachim Desroches) + * Type __locale_system: Fix version comparison (Dennis Camera) + * Type __unpack: Add --onchange parameter (Ander Punnar) + * Type __download: Fix manual (Ander Punnar) + * Explorer os_version: Convert Debian sid to version number (Dennis Camera) + * Core: Expand require delimiter characters, split by consecutive delimiters (Darko Poljak) + * Type __timezone: Make singleton (Dennis Camera) + * Type __systemd_service: Fix manpage typos (Matthias Stecher) + +6.7.0: 2020-07-28 + * Delete deprecated type: __pf_apply (Darko Poljak) + * New type: __download (Ander Punnar) + * Type __locale_system: Add devuan support (Dennis Camera) + * Type __package_opkg: Add locking (Dennis Camera) + * Type __hosts: Add --alias parameter (Dennis Camera) + * Type __user: Fix shadow explorer for OpenBSD (Dennis Camera) + * Core: Make emulator-part code consistent; remove faulty warning (Darko Poljak) + * Types __file, __directory: Support setuid, setguid, sticky bits (Dennis Camera) + * Type __postfix_master: Fix --option parameter and option expansion (Daniel Fancsali) + * Type __user: Install user packages on OpenWrt (Dennis Camera) + * Type __openldap_server: Add Alpine support (Timothée Floure) + * Type __package_apt: Fix for legacy APT versions that do not support --no-install-recommends (Dennis Camera) + * Type __key_value: Get awk from POSIX PATH (Dennis Camera) + * New type: __unpack (Ander Punnar) + * Type __locale_system: Support more OSes (Dennis Camera) + * Explorers cpu_cores, disks, memory: Fix for NetBSD (Dennis Camera) + * Type __sysctl: Fix for NetBSD (Dennis Camera) + +6.6.0: 2020-06-17 + * Type __ssh_authorized_keys: Add option for removing undefined keys (Ander Punnar) + * Core: Support colored log output (Evil Ham) + * Core: Tune colored log output and respect NO_COLOR env var (Dennis Camera) + * Documentation: Fix failing man pages build with newer sphinx versions (Darko Poljak) + * Documentation: Fix trivial grammatical mistakes (Jaak Ristioja) + * Explorer os: Fix for sles15 (Daniel Heule) + * Type __clean_path: Add --path parameter (Ander Punnar) + * Core: Increase minimal supported Python version to 3.5 (Darko Poljak) + * Core: Add log server for nested logging (Steven Armstrong) + +6.5.6: 2020-05-25 + * Type __pyvenv: Switch to python3 -m venv for Ubuntu (Nico Schottelius) + * Type __letsencrypt_cert: Whitelist Ubuntu (Nico Schottelius) + * Types __cron, __file, __link: Improve manpages (Matthias Stecher) + * Explorer machine_type: Add support for FreeBSD and OpenBSD, and simplify Linux code (Evil Ham) + * Type __ssh_authorized_key, __ssh_authorized_keys: Improve manpages (Evil Ham) + * Type __ssh_authorized_keys: Fix bug where --option was not multiple (Evil Ham) + * Type __motd: Debian/Ubuntu/Devuan use /etc/motd (Ander Punnar) + * Type __group: Fix --gid on FreeBSD (Ander Punnar) + * Configuration: Fix typos in cdist.cfg.skeleton (Jaak Ristioja) + * Type __user: Fix user deletion on FreeBSD (Ander Punnar) + * Core: Fix double log lines (Darko Poljak) + +6.5.5: 2020-05-01 * Core: Fix XDG_CONFIG_HOME config file location (Joachim Desroches) * Type __postgres_database: Add encoding, lc-collate, lc-ctype, template parameters (Timothée Floure) * Type __motd: Improve documentation and support for FreeBSD (Evil Ham) @@ -10,6 +71,9 @@ next: * New type: __pf_apply_anchor (Kamila Součková, Evil Ham) * Type __pf_ruleset: Refactor (Kamila Součková, Evil Ham) * Type __pf_apply: Deprecate type (Kamila Součková, Evil Ham) + * Configuration: Add notes to cdist.cfg.skeleton (Evil Ham) + * Explorers cpu_cores, memory: Improve *BSD support (Evil Ham) + * Core: Remove debug logging noise (Evil Ham) 6.5.4: 2020-04-11 * Explorer init: Do not grep on non-existent init (Steven Armstrong) diff --git a/docs/src/Makefile b/docs/src/Makefile index 2ecf7a32..ba87d170 100644 --- a/docs/src/Makefile +++ b/docs/src/Makefile @@ -2,10 +2,10 @@ # # You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = ../dist +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +PAPER ?= +BUILDDIR ?= ../dist # for cache, etc. _BUILDDIR = _build diff --git a/docs/src/cdist-bootstrap.rst b/docs/src/cdist-bootstrap.rst index c9972a99..10f86c5a 100644 --- a/docs/src/cdist-bootstrap.rst +++ b/docs/src/cdist-bootstrap.rst @@ -25,7 +25,7 @@ people, have a look at `cdist best practice `_. Setup working directory and branch ---------------------------------- I assume you have a fresh copy of the cdist tree in ~/cdist, cloned from -one of the official urls (see `cdist quickstart `_ if you don't). +one of the official URLs (see `cdist quickstart `_ if you don't). Entering the command "git branch" should show you "* master", which indicates you are on the **master** branch. diff --git a/docs/src/cdist-cache.rst b/docs/src/cdist-cache.rst index 0e5361ee..d2d2d56c 100644 --- a/docs/src/cdist-cache.rst +++ b/docs/src/cdist-cache.rst @@ -59,7 +59,7 @@ typeorder Object cache overview ~~~~~~~~~~~~~~~~~~~~~ -Each object under :strong:`object` directory has its own structurue. +Each object under :strong:`object` directory has its own structure. code-local code generated from gencode-local, present only if something is diff --git a/docs/src/cdist-configuration.rst b/docs/src/cdist-configuration.rst index 706ed761..a2024584 100644 --- a/docs/src/cdist-configuration.rst +++ b/docs/src/cdist-configuration.rst @@ -21,7 +21,7 @@ precedence. Configuration option value read from source with higher precedence will overwrite option value, if exists, read from source with lower precedence. That means that command-line option wins them all. -Users can decide on the local conifguration file location. It can be either +Users can decide on the local configuration file location. It can be either ~/.cdist.cfg or $XDG_CONFIG_HOME/cdist/cdist.cfg. Note that, if both exist, then ~/.cdist.cfg is used. diff --git a/docs/src/cdist-install.rst b/docs/src/cdist-install.rst index 880b9f8e..6f4f14d7 100644 --- a/docs/src/cdist-install.rst +++ b/docs/src/cdist-install.rst @@ -9,15 +9,15 @@ Source Host This is the machine from which you will configure target hosts. - * /bin/sh: A posix like shell (for instance bash, dash, zsh) - * Python >= 3.2 + * /bin/sh: A POSIX like shell (for instance bash, dash, zsh) + * Python >= 3.5 * SSH client * sphinx (for building html docs and/or the man pages) Target Hosts ~~~~~~~~~~~~ - * /bin/sh: A posix like shell (for instance bash, dash, zsh) + * /bin/sh: A POSIX like shell (for instance bash, dash, zsh) * SSH server Install cdist diff --git a/docs/src/cdist-manifest.rst b/docs/src/cdist-manifest.rst index 5dbca479..2e49a721 100644 --- a/docs/src/cdist-manifest.rst +++ b/docs/src/cdist-manifest.rst @@ -95,7 +95,8 @@ Dependencies ------------ If you want to describe that something requires something else, just setup the variable "require" to contain the requirements. Multiple -requirements can be added white space separated. +requirements can be added separated with (optionally consecutive) +delimiters including space, tab and newline. :: diff --git a/docs/src/cdist-real-world.rst b/docs/src/cdist-real-world.rst index ba118d63..973dcd6d 100644 --- a/docs/src/cdist-real-world.rst +++ b/docs/src/cdist-real-world.rst @@ -198,7 +198,7 @@ We require package uWSGI present in order to create **/etc/uwsgi/apps-enabled/$u Installation of uWSGI also creates configuration layout: **/etc/uwsgi/apps-enabled**. If this directory does not exist then **__file** type would error. We also use stdin as file content source. For details see `Input from stdin `_. -For feading stdin we use here-document (**<<** operator). It allows redirection of subsequent +For feeding stdin we use here-document (**<<** operator). It allows redirection of subsequent lines read by the shell to the input of a command until a line containing only the delimiter and a newline, with no blank characters in between (EOF in our case). @@ -546,7 +546,7 @@ we have changed our **wsgi.py** file uWSGI reloads the application. Our application selects and lists items from **items** table. -Openning application +Opening application ~~~~~~~~~~~~~~~~~~~~ Finally try the application:: diff --git a/docs/src/cdist-reference.rst.sh b/docs/src/cdist-reference.rst.sh index e77d98f6..c0ac2c3e 100755 --- a/docs/src/cdist-reference.rst.sh +++ b/docs/src/cdist-reference.rst.sh @@ -34,7 +34,7 @@ dest="$__cdist_abs_mydir/$filename" cd "$__cdist_abs_mydir" exec > "$dest" -cat << eof +cat << eof Reference ========= Variable, path and type reference for cdist @@ -51,7 +51,7 @@ eof done ) -cat << eof +cat << eof Paths ----- @@ -187,13 +187,13 @@ usable within a object directory: files This directory is reserved for user data and will not be used - by cdist at any time. It can be used freely by the type + by cdist at any time. It can be used freely by the type (for instance to store template results). changed This empty file exists in an object directory, if the object has code to be executed (either remote or local). stdin - This file exists and contains data, if data was provided on stdin + This file exists and contains data, if data was provided on stdin when the type was called. @@ -222,65 +222,89 @@ __cdist_log_level, __cdist_log_level_name | TRACE | 5 | +----------------+-----------------+ + Available for: initial manifest, explorer, type manifest, type explorer, + type gencode. +__cdist_colored_log + whether or not cdist's log has colors enabled. + Is set to the string ``true`` if cdist's output is using colors, + otherwise the variable contains the string ``false``. + Available for: initial manifest, explorer, type manifest, type explorer, type gencode. __cdist_dry_run Is set only when doing dry run (``-n`` flag). + Available for: initial manifest, explorer, type manifest, type explorer, type gencode. __explorer Directory that contains all global explorers. + Available for: initial manifest, explorer, type explorer, shell. __files Directory that contains content from the "files" subdirectories from the configuration directories. + Available for: initial manifest, type manifest, type gencode, shell. __manifest Directory that contains the initial manifest. + Available for: initial manifest, type manifest, shell. __global Directory that contains generic output like explorer. + Available for: initial manifest, type manifest, type gencode, shell. __messages_in File to read messages from. + Available for: initial manifest, type manifest, type gencode. __messages_out File to write messages. + Available for: initial manifest, type manifest, type gencode. __object Directory that contains the current object. + Available for: type manifest, type explorer, type gencode and code scripts. __object_id The type unique object id. + Available for: type manifest, type explorer, type gencode and code scripts. - Note: The leading and the trailing "/" will always be stripped (caused by - the filesystem database and ensured by the core). - Note: Double slashes ("//") will not be fixed and result in an error. + + | Note: The leading and the trailing "/" will always be stripped (caused by + the filesystem database and ensured by the core). + | Note: Double slashes ("//") will not be fixed and result in an error. __object_name The full qualified name of the current object. + Available for: type manifest, type explorer, type gencode. __target_host The host we are deploying to. This is primary variable. It's content is literally the one user passed in. + Available for: explorer, initial manifest, type explorer, type manifest, type gencode, shell. __target_hostname The hostname of host we are deploying to. This variable is derived from **__target_host** (using **socket.getaddrinfo(__target_host)** and then **socket.gethostbyaddr()**). + Available for: explorer, initial manifest, type explorer, type manifest, type gencode, shell. __target_fqdn The fully qualified domain name of the host we are deploying to. This variable is derived from **__target_host** (using **socket.getfqdn()**). + Available for: explorer, initial manifest, type explorer, type manifest, type gencode, shell. __target_host_tags Comma separated list of target host tags. + Available for: explorer, initial manifest, type explorer, type manifest, type gencode, shell. __type Path to the current type. + Available for: type manifest, type gencode. __type_explorer Directory that contains the type explorers. + Available for: type explorer. Environment variables (for writing) @@ -344,6 +368,11 @@ CDIST_INVENTORY_DIR CDIST_BETA Enable beta functionalities. +CDIST_COLORED_OUTPUT + Colorize cdist's output. If enabled, cdist will use different colors for + different log levels. + Recognized values are 'always', 'never', and 'auto' (the default). + CDIST_CACHE_PATH_PATTERN Custom cache path pattern. eof diff --git a/docs/src/cdist-remote-exec-copy.rst b/docs/src/cdist-remote-exec-copy.rst index bb818310..e7b7b226 100644 --- a/docs/src/cdist-remote-exec-copy.rst +++ b/docs/src/cdist-remote-exec-copy.rst @@ -10,7 +10,7 @@ By default this is accomplished with ssh and scp respectively. The default implementations used by cdist are:: __remote_exec: ssh -o User=root - __remote_copy: scp -o User=root + __remote_copy: scp -o User=root -q The user can override these defaults by providing custom implementations and passing them to cdist with the --remote-exec and/or --remote-copy arguments. @@ -26,3 +26,390 @@ specified by enclosed in square brackets (see :strong:`ssh`\ (1) and With this simple interface the user can take total control of how cdist interacts with the target when required, while the default implementation remains as simple as possible. + + +Examples +-------- + +Here are examples of using alternative __remote_copy and __remote_exec scripts. + +All scripts from below are present in cdist sources in `other/examples/remote` +directory. + +ssh +~~~ + +Same as cdist default. + +**copy** + +Usage: cdist config --remote-copy "/path/to/this/script" target_host + +.. code-block:: sh + + #echo "$@" | logger -t "cdist-ssh-copy" + scp -o User=root -q $@ + +**exec** + +Usage: cdist config --remote-exec "/path/to/this/script" target_host + +.. code-block:: sh + + #echo "$@" | logger -t "cdist-ssh-exec" + ssh -o User=root $@ + +local +~~~~~ + +This effectively turns remote calling into local calling. Probably most useful +for the unit testing. + +**copy** + +.. code-block:: sh + + code="$(echo "$@" | sed "s|\([[:space:]]\)$__target_host:|\1|g")" + cp -L $code + +**exec** + +.. code-block:: sh + + target_host=$1; shift + echo "$@" | /bin/sh + +chroot +~~~~~~ + +**copy** + +Usage: cdist config --remote-copy "/path/to/this/script /path/to/your/chroot" target-id + +.. code-block:: sh + + log() { + #echo "$@" | logger -t "cdist-chroot-copy" + : + } + + chroot="$1"; shift + target_host="$__target_host" + + # replace target_host with chroot location + code="$(echo "$@" | sed "s|$target_host:|$chroot|g")" + + log "target_host: $target_host" + log "chroot: $chroot" + log "$@" + log "$code" + + # copy files into chroot + cp $code + + log "-----" + +**exec** + +Usage: cdist config --remote-exec "/path/to/this/script /path/to/your/chroot" target-id + +.. code-block:: sh + + log() { + #echo "$@" | logger -t "cdist-chroot-exec" + : + } + + chroot="$1"; shift + target_host="$1"; shift + + script=$(mktemp "${chroot}/tmp/chroot-${0##*/}.XXXXXXXXXX") + trap cleanup INT TERM EXIT + cleanup() { + [ $__cdist_debug ] || rm "$script" + } + + log "target_host: $target_host" + log "script: $script" + log "@: $@" + echo "#!/bin/sh -l" > "$script" + echo "$@" >> "$script" + chmod +x "$script" + + relative_script="${script#$chroot}" + log "relative_script: $relative_script" + + # run in chroot + chroot "$chroot" "$relative_script" + + log "-----" + +rsync +~~~~~ + +**copy** + +Usage: cdist config --remote-copy /path/to/this/script target_host + +.. code-block:: sh + + # For rsync to do the right thing, the source has to end with "/" if it is + # a directory. The below preprocessor loop takes care of that. + + # second last argument is the source + source_index=$(($#-1)) + index=0 + for arg in $@; do + if [ $index -eq 0 ]; then + # reset $@ + set -- + fi + index=$((index+=1)) + if [ $index -eq $source_index -a -d "$arg" ]; then + arg="${arg%/}/" + fi + set -- "$@" "$arg" + done + + rsync --backup --suffix=~cdist -e 'ssh -o User=root' $@ + +schroot +~~~~~~~ + +__remote_copy and __remote_exec scripts to run cdist against a chroot on the +target host over ssh. + +**copy** + +Usage: cdist config --remote-copy "/path/to/this/script schroot-chroot-name" target_host + + +.. code-block:: sh + + log() { + #echo "$@" | logger -t "cdist-schroot-copy" + : + } + + chroot_name="$1"; shift + target_host="$__target_host" + + # get directory for given chroot_name + chroot="$(ssh -o User=root -q $target_host schroot -c $chroot_name --config | awk -F = '/directory=/ {print $2}')" + + # prefix destination with chroot + code="$(echo "$@" | sed "s|$target_host:|$target_host:$chroot|g")" + + log "target_host: $target_host" + log "chroot_name: $chroot_name" + log "chroot: $chroot" + log "@: $@" + log "code: $code" + + # copy files into remote chroot + scp -o User=root -q $code + + log "-----" + +**exec** + +Usage: cdist config --remote-exec "/path/to/this/script schroot-chroot-name" target_host + +.. code-block:: sh + + log() { + #echo "$@" | logger -t "cdist-schroot-exec" + : + } + + chroot_name="$1"; shift + target_host="$1"; shift + + code="ssh -o User=root -q $target_host schroot -c $chroot_name -- $@" + + log "target_host: $target_host" + log "chroot_name: $chroot_name" + log "@: $@" + log "code: $code" + + # run in remote chroot + $code + + log "-----" + +schroot-uri +~~~~~~~~~~~ + +__remote_exec/__remote_copy script to run cdist against a schroot target URI. + +Usage:: + + cdist config \ + --remote-exec "/path/to/this/script exec" \ + --remote-copy "/path/to/this/script copy" \ + target_uri + + # target_uri examples: + schroot:///chroot-name + schroot://foo.ethz.ch/chroot-name + schroot://user-name@foo.ethz.ch/chroot-name + + # and how to match them in .../manifest/init + case "$target_host" in + schroot://*) + # any schroot + ;; + schroot://foo.ethz.ch/*) + # any schroot on specific host + ;; + schroot://foo.ethz.ch/chroot-name) + # specific schroot on specific host + ;; + schroot:///chroot-name) + # specific schroot on localhost + ;; + esac + +**copy/exec** + +.. code-block:: sh + + my_name="${0##*/}" + mode="$1"; shift + + log() { + # uncomment me for debugging + #echo "$@" | logger -t "cdist-$my_name-$mode" + : + } + + die() { + echo "$@" >&2 + exit 1 + } + + + uri="$__target_host" + + scheme="${uri%%:*}"; rest="${uri#$scheme:}"; rest="${rest#//}" + authority="${rest%%/*}"; rest="${rest#$authority}" + path="${rest%\?*}"; rest="${rest#$path}" + schroot_name="${path#/}" + + [ "$scheme" = "schroot" ] || die "Failed to parse scheme from __target_host ($__target_host). Expected 'schroot', got '$scheme'" + [ -n "$schroot_name" ] || die "Failed to parse schroot name from __target_host: $__target_host" + + case "$authority" in + '') + # authority is empty, neither user nor host given + user="" + host="" + ;; + *@*) + # authority contains @, take user from authority + user="${authority%@*}" + host="${authority#*@}" + ;; + *) + # no user in authority, default to root + user="root" + host="$authority" + ;; + esac + + log "mode: $mode" + log "@: $@" + log "uri: $uri" + log "scheme: $scheme" + log "authority: $authority" + log "user: $user" + log "host: $host" + log "path: $path" + log "schroot_name: $schroot_name" + + exec_prefix="" + copy_prefix="" + if [ -n "$host" ]; then + # we are working on a remote host + exec_prefix="ssh -o User=$user -q $host" + copy_prefix="scp -o User=$user -q" + copy_destination_prefix="$host:" + else + # working on local machine + copy_prefix="cp" + copy_destination_prefix="" + fi + log "exec_prefix: $exec_prefix" + log "copy_prefix: $copy_prefix" + log "copy_destination_prefix: $copy_destination_prefix" + + case "$mode" in + exec) + # In exec mode the first argument is the __target_host which we already got from env. Get rid of it. + shift + code="$exec_prefix schroot -c $schroot_name -- sh -c '$@'" + ;; + copy) + # get directory for given chroot_name + schroot_directory="$($exec_prefix schroot -c $schroot_name --config | awk -F = '/directory=/ {print $2}')" + [ -n "$schroot_directory" ] || die "Failed to retreive schroot directory for schroot: $schroot_name" + log "schroot_directory: $schroot_directory" + # prefix destination with chroot + code="$copy_prefix $(echo "$@" | sed "s|$uri:|${copy_destination_prefix}${schroot_directory}|g")" + ;; + *) die "Unknown mode: $mode";; + esac + + log "code: $code" + + # Run the code + $code + + log "-----" + +sudo +~~~~ + +**copy** + +Use rsync over ssh to copy files. Uses the "--rsync-path" option +to run the remote rsync instance with sudo. + +This command assumes your ssh configuration is already set up in ~/.ssh/config. + +Usage: cdist config --remote-copy /path/to/this/script target_host + +.. code-block:: sh + + # For rsync to do the right thing, the source has to end with "/" if it is + # a directory. The below preprocessor loop takes care of that. + + # second last argument is the source + source_index=$(($#-1)) + index=0 + for arg in $@; do + if [ $index -eq 0 ]; then + # reset $@ + set -- + fi + index=$((index+=1)) + if [ $index -eq $source_index -a -d "$arg" ]; then + arg="${arg%/}/" + fi + set -- "$@" "$arg" + done + + rsync --copy-links --rsync-path="sudo rsync" -e 'ssh' "$@" + +**exec** + +Prefixes all remote commands with sudo. + +This command assumes your ssh configuration is already set up in ~/.ssh/config. + +Usage: cdist config --remote-exec "/path/to/this/script" target_host + +.. code-block:: sh + + host="$1"; shift + ssh -q "$host" sudo sh -c \""$@"\" diff --git a/docs/src/cdist-support.rst b/docs/src/cdist-support.rst index 19afde2f..f9f61f01 100644 --- a/docs/src/cdist-support.rst +++ b/docs/src/cdist-support.rst @@ -3,7 +3,7 @@ Support Chat ~~~~ -Chat with us: `ungleich chat `_. +Chat with us on `#cdist:ungleich.ch `_. Mailing list ~~~~~~~~~~~~ diff --git a/docs/src/cdist-upgrade.rst b/docs/src/cdist-upgrade.rst index 67fd4934..f745b212 100644 --- a/docs/src/cdist-upgrade.rst +++ b/docs/src/cdist-upgrade.rst @@ -14,7 +14,7 @@ To upgrade cdist in the current branch use make man export MANPATH=$MANPATH:$(pwd -P)/doc/man -If you stay on a version branche (i.e. 1.0, 1.1., ...), nothing should break. +If you stay on a version branch (i.e. 1.0, 1.1., ...), nothing should break. The master branch on the other hand is the development branch and may not be working, break your setup or eat the tree in your garden. @@ -61,7 +61,7 @@ After that, you can go back and continue the upgrade: Update the python package ------------------------- -To upgrade to the lastet version do +To upgrade to the latest version do .. code-block:: sh @@ -158,7 +158,7 @@ Updating from 1.5 to 1.6 * If you used **\_\_package_apt --preseed**, you need to use the new type **\_\_debconf_set_selections** instead. * The **\_\_package** types accepted either --state deinstalled or - --state uninstaaled. Starting with 1.6, it was made consistently + --state uninstalled. Starting with 1.6, it was made consistently to --state removed. Updating from 1.3 to 1.5 diff --git a/docs/src/cdist-why.rst b/docs/src/cdist-why.rst index 1123a1de..0e2cd34d 100644 --- a/docs/src/cdist-why.rst +++ b/docs/src/cdist-why.rst @@ -21,7 +21,7 @@ Not only is shell scripting widely known by system engineers, but it is also a very powerful language. Here are some features which make daily work easy: - * Configuration can react dynamicly on explored values + * Configuration can react dynamically on explored values * High level string manipulation (using sed, awk, grep) * Conditional support (**if, case**) * Loop support (**for, while**) @@ -44,7 +44,7 @@ Cdist requires very little on a target system. Even better, in almost all cases all dependencies are usually fulfilled. Cdist does not require an agent or high level programming languages on the target host: it will run on any host that -has a **ssh server running** and a posix compatible shell +has a **ssh server running** and a POSIX compatible shell (**/bin/sh**). Compared to other configuration management systems, it does not require to open up an additional port. diff --git a/docs/src/man1/cdist.rst b/docs/src/man1/cdist.rst index 38248821..0ecb4a61 100644 --- a/docs/src/man1/cdist.rst +++ b/docs/src/man1/cdist.rst @@ -15,52 +15,51 @@ SYNOPSIS cdist banner [-h] [-l LOGLEVEL] [-q] [-v] - cdist config [-h] [-l LOGLEVEL] [-q] [-v] [-b] [-g CONFIG_FILE] [-4] - [-6] [-C CACHE_PATH_PATTERN] [-c CONF_DIR] [-i MANIFEST] - [-j [JOBS]] [-n] [-o OUT_PATH] [-P] - [-R [{tar,tgz,tbz2,txz}]] [-r REMOTE_OUT_PATH] - [--remote-copy REMOTE_COPY] [--remote-exec REMOTE_EXEC] - [-S] [-I INVENTORY_DIR] [-A] [-a] [-f HOSTFILE] - [-p [HOST_MAX]] [-s] [-t] + cdist config [-h] [-l LOGLEVEL] [-q] [-v] [-b] [--colors WHEN] + [-g CONFIG_FILE] [-4] [-6] [-C CACHE_PATH_PATTERN] + [-c CONF_DIR] [-i MANIFEST] [-j [JOBS]] [--log-server] + [-n] [-o OUT_PATH] [-P] [-R [{tar,tgz,tbz2,txz}]] + [-r REMOTE_OUT_PATH] [--remote-copy REMOTE_COPY] + [--remote-exec REMOTE_EXEC] [-S] [-I INVENTORY_DIR] [-A] + [-a] [-f HOSTFILE] [-p [HOST_MAX]] [-s] [-t] [host [host ...]] - cdist install [-h] [-l LOGLEVEL] [-q] [-v] [-b] [-g CONFIG_FILE] [-4] - [-6] [-C CACHE_PATH_PATTERN] [-c CONF_DIR] [-i MANIFEST] - [-j [JOBS]] [-n] [-o OUT_PATH] [-P] - [-R [{tar,tgz,tbz2,txz}]] [-r REMOTE_OUT_PATH] - [--remote-copy REMOTE_COPY] [--remote-exec REMOTE_EXEC] - [-S] [-I INVENTORY_DIR] [-A] [-a] [-f HOSTFILE] - [-p [HOST_MAX]] [-s] [-t] + cdist install [-h] [-l LOGLEVEL] [-q] [-v] [-b] [--colors WHEN] + [-g CONFIG_FILE] [-4] [-6] [-C CACHE_PATH_PATTERN] + [-c CONF_DIR] [-i MANIFEST] [-j [JOBS]] [--log-server] + [-n] [-o OUT_PATH] [-P] [-R [{tar,tgz,tbz2,txz}]] + [-r REMOTE_OUT_PATH] [--remote-copy REMOTE_COPY] + [--remote-exec REMOTE_EXEC] [-S] [-I INVENTORY_DIR] [-A] + [-a] [-f HOSTFILE] [-p [HOST_MAX]] [-s] [-t] [host [host ...]] cdist inventory [-h] {add-host,add-tag,del-host,del-tag,list} ... - cdist inventory add-host [-h] [-l LOGLEVEL] [-q] [-v] [-b] - [-g CONFIG_FILE] [-I INVENTORY_DIR] - [-f HOSTFILE] + cdist inventory add-host [-h] [-l LOGLEVEL] [-q] [-v] [-b] [--colors WHEN] + [-g CONFIG_FILE] [-I INVENTORY_DIR] [-f HOSTFILE] [host [host ...]] - cdist inventory add-tag [-h] [-l LOGLEVEL] [-q] [-v] [-b] - [-g CONFIG_FILE] [-I INVENTORY_DIR] - [-f HOSTFILE] [-T TAGFILE] [-t TAGLIST] + cdist inventory add-tag [-h] [-l LOGLEVEL] [-q] [-v] [-b] [--colors WHEN] + [-g CONFIG_FILE] [-I INVENTORY_DIR] [-f HOSTFILE] + [-T TAGFILE] [-t TAGLIST] [host [host ...]] - cdist inventory del-host [-h] [-l LOGLEVEL] [-q] [-v] [-b] + cdist inventory del-host [-h] [-l LOGLEVEL] [-q] [-v] [-b] [--colors WHEN] [-g CONFIG_FILE] [-I INVENTORY_DIR] [-a] [-f HOSTFILE] [host [host ...]] - cdist inventory del-tag [-h] [-l LOGLEVEL] [-q] [-v] [-b] + cdist inventory del-tag [-h] [-l LOGLEVEL] [-q] [-v] [-b] [--colors WHEN] [-g CONFIG_FILE] [-I INVENTORY_DIR] [-a] [-f HOSTFILE] [-T TAGFILE] [-t TAGLIST] [host [host ...]] - cdist inventory list [-h] [-l LOGLEVEL] [-q] [-v] [-b] [-g CONFIG_FILE] - [-I INVENTORY_DIR] [-a] [-f HOSTFILE] [-H] [-t] + cdist inventory list [-h] [-l LOGLEVEL] [-q] [-v] [-b] [--colors WHEN] + [-g CONFIG_FILE] [-I INVENTORY_DIR] [-a] [-f HOSTFILE] + [-H] [-t] [host [host ...]] - cdist preos [-h] [-l LOGLEVEL] [-q] [-v] [-c CONF_DIR] [-g CONFIG_FILE] - [-L] + cdist preos [-h] [-l LOGLEVEL] [-q] [-v] [-c CONF_DIR] [-g CONFIG_FILE] [-L] [preos] ... cdist preos [preos-options] debian [-h] [-l LOGLEVEL] [-q] [-v] [-b] [-a ARCH] [-B] @@ -84,9 +83,10 @@ SYNOPSIS [-S SCRIPT] [-s SUITE] [-y REMOTE_COPY] target_dir - cdist shell [-h] [-l LOGLEVEL] [-q] [-v] [-s SHELL] + cdist shell [-h] [-l LOGLEVEL] [-q] [-v] [--colors WHEN] [-s SHELL] - cdist info [-h] [-a] [-c CONF_DIR] [-e] [-F] [-f] [-g CONFIG_FILE] [-t] [pattern] + cdist info [-h] [-a] [-c CONF_DIR] [-e] [-F] [-f] [-g CONFIG_FILE] [-t] + [pattern] DESCRIPTION @@ -95,7 +95,7 @@ cdist is the frontend executable to the cdist configuration management. It supports different subcommands as explained below. It is written in Python so it requires :strong:`python`\ (1) to be installed. -It requires a minimal Python version 3.2. +It requires a minimal Python version 3.5. GENERAL ------- @@ -104,6 +104,14 @@ All commands accept the following options: **-h, --help** Show the help screen. +**--colors WHEN** + Colorize cdist's output. If enabled, cdist will use different colors for + different log levels. + WHEN recognizes the values 'always', 'never', and 'auto' (the default). + + If the value is 'auto', colored output is enabled if stdout is a TTY + unless the NO_COLOR (https://no-color.org/) environment variable is defined. + **-l LOGLEVEL, --log-level LOGLEVEL** Set the specified verbosity level. The levels, in order from the lowest to the highest, are: ERROR (-1), @@ -156,7 +164,7 @@ Install command is currently in beta. **-b, --beta** Enable beta functionality. - + **-C CACHE_PATH_PATTERN, --cache-path-pattern CACHE_PATH_PATTERN** Specify custom cache path pattern. If it is not set then default hostdir is used. For more info on format see @@ -169,17 +177,15 @@ Install command is currently in beta. **-f HOSTFILE, --file HOSTFILE** Read specified file for a list of additional hosts to operate on - or if '-' is given, read stdin (one host per line). - If no host or host file is specified then, by default, - read hosts from stdin. For the file format see - :strong:`HOSTFILE FORMAT` below. + or if '-' is given, read stdin (one host per line). For the file + format see :strong:`HOSTFILE FORMAT` below. **-g CONFIG_FILE, --config-file CONFIG_FILE** Use specified custom configuration file. **-I INVENTORY_DIR, --inventory INVENTORY_DIR** Use specified custom inventory directory. Inventory - directory is set up by the following rules: if cdist + directory is set up by the following rules: if cdist configuration resolves this value then specified directory is used, if HOME env var is set then ~/.cdit/inventory is used, otherwise distribution @@ -194,6 +200,12 @@ Install command is currently in beta. are supported. Without argument CPU count is used by default. +**--log-server** + Start a log server for sub processes to use. This is + mainly useful when running cdist nested from a code- + local script. Log server is always implicitly started + for 'install' command. + **-n, --dry-run** Do not execute code. @@ -285,16 +297,15 @@ Add host(s) to inventory database. **-f HOSTFILE, --file HOSTFILE** Read additional hosts to add from specified file or - from stdin if '-' (each host on separate line). If no - host or host file is specified then, by default, read - from stdin. Hostfile format is the same as config hostfile format. + from stdin if '-' (each host on separate line). + Hostfile format is the same as config hostfile format. **-g CONFIG_FILE, --config-file CONFIG_FILE** Use specified custom configuration file. **-I INVENTORY_DIR, --inventory INVENTORY_DIR** Use specified custom inventory directory. Inventory - directory is set up by the following rules: if cdist + directory is set up by the following rules: if cdist configuration resolves this value then specified directory is used, if HOME env var is set then ~/.cdit/inventory is used, otherwise distribution @@ -313,18 +324,15 @@ Add tag(s) to inventory database. **-f HOSTFILE, --file HOSTFILE** Read additional hosts to add tags from specified file - or from stdin if '-' (each host on separate line). If - no host or host file is specified then, by default, - read from stdin. If no tags/tagfile nor hosts/hostfile - are specified then tags are read from stdin and are - added to all hosts. Hostfile format is the same as config hostfile format. + or from stdin if '-' (each host on separate line). + Hostfile format is the same as config hostfile format. **-g CONFIG_FILE, --config-file CONFIG_FILE** Use specified custom configuration file. **-I INVENTORY_DIR, --inventory INVENTORY_DIR** Use specified custom inventory directory. Inventory - directory is set up by the following rules: if cdist + directory is set up by the following rules: if cdist configuration resolves this value then specified directory is used, if HOME env var is set then ~/.cdit/inventory is used, otherwise distribution @@ -332,11 +340,8 @@ Add tag(s) to inventory database. **-T TAGFILE, --tag-file TAGFILE** Read additional tags to add from specified file or - from stdin if '-' (each tag on separate line). If no - tag or tag file is specified then, by default, read - from stdin. If no tags/tagfile nor hosts/hostfile are - specified then tags are read from stdin and are added - to all hosts. Tagfile format is the same as config hostfile format. + from stdin if '-' (each tag on separate line). + Tagfile format is the same as config hostfile format. **-t TAGLIST, --taglist TAGLIST** Tag list to be added for specified host(s), comma @@ -358,16 +363,15 @@ Delete host(s) from inventory database. **-f HOSTFILE, --file HOSTFILE** Read additional hosts to delete from specified file or - from stdin if '-' (each host on separate line). If no - host or host file is specified then, by default, read - from stdin. Hostfile format is the same as config hostfile format. + from stdin if '-' (each host on separate line). + Hostfile format is the same as config hostfile format. **-g CONFIG_FILE, --config-file CONFIG_FILE** Use specified custom configuration file. **-I INVENTORY_DIR, --inventory INVENTORY_DIR** Use specified custom inventory directory. Inventory - directory is set up by the following rules: if cdist + directory is set up by the following rules: if cdist configuration resolves this value then specified directory is used, if HOME env var is set then ~/.cdit/inventory is used, otherwise distribution @@ -390,18 +394,15 @@ Delete tag(s) from inventory database. **-f HOSTFILE, --file HOSTFILE** Read additional hosts to delete tags for from specified file or from stdin if '-' (each host on - separate line). If no host or host file is specified - then, by default, read from stdin. If no tags/tagfile - nor hosts/hostfile are specified then tags are read - from stdin and are deleted from all hosts. Hostfile - format is the same as config hostfile format. + separate line). Hostfile format is the same as + config hostfile format. **-g CONFIG_FILE, --config-file CONFIG_FILE** Use specified custom configuration file. **-I INVENTORY_DIR, --inventory INVENTORY_DIR** Use specified custom inventory directory. Inventory - directory is set up by the following rules: if cdist + directory is set up by the following rules: if cdist configuration resolves this value then specified directory is used, if HOME env var is set then ~/.cdit/inventory is used, otherwise distribution @@ -409,11 +410,8 @@ Delete tag(s) from inventory database. **-T TAGFILE, --tag-file TAGFILE** Read additional tags from specified file or from stdin - if '-' (each tag on separate line). If no tag or tag - file is specified then, by default, read from stdin. - If no tags/tagfile nor hosts/hostfile are specified - then tags are read from stdin and are added to all - hosts. Tagfile format is the same as config hostfile format. + if '-' (each tag on separate line). + Tagfile format is the same as config hostfile format. **-t TAGLIST, --taglist TAGLIST** Tag list to be deleted for specified host(s), comma @@ -448,7 +446,7 @@ List inventory database. **-I INVENTORY_DIR, --inventory INVENTORY_DIR** Use specified custom inventory directory. Inventory - directory is set up by the following rules: if cdist + directory is set up by the following rules: if cdist configuration resolves this value then specified directory is used, if HOME env var is set then ~/.cdit/inventory is used, otherwise distribution @@ -673,6 +671,9 @@ The possible keywords and their meanings are as follows: :strong:`cache_path_pattern` Specify cache path pattern. +:strong:`colored_output` + Colorize cdist's output. cf. the :code:`--colors` option. + :strong:`conf_dir` List of configuration directories separated with the character conventionally used by the operating system to separate search path components (as in PATH), @@ -726,7 +727,7 @@ The possible keywords and their meanings are as follows: in the format: YYYYMMDDHHMMSS.us. :strong:`verbosity` - Set verbosity level. Valid values are: + Set verbosity level. Valid values are: 'ERROR', 'WARNING', 'INFO', 'VERBOSE', 'DEBUG', 'TRACE' and 'OFF'. @@ -758,7 +759,7 @@ cdist/preos NOTES ----- cdist detects if host is specified by IPv6 address. If so then remote_copy -command is executed with host address enclosed in square brackets +command is executed with host address enclosed in square brackets (see :strong:`scp`\ (1)). EXAMPLES @@ -893,6 +894,9 @@ CDIST_BETA CDIST_CACHE_PATH_PATTERN Custom cache path pattern. +CDIST_COLORED_OUTPUT + Colorize cdist's output. cf. the :code:`--colors` option. + CDIST_CONFIG_FILE Custom configuration file. @@ -955,5 +959,5 @@ such case and display a warning message. An example of such a case: COPYING ------- -Copyright \(C) 2011-2019 Nico Schottelius. Free use of this software is +Copyright \(C) 2011-2020 Nico Schottelius. Free use of this software is granted under the terms of the GNU General Public License v3 or later (GPLv3+). diff --git a/scripts/cdist b/scripts/cdist index 7bf12c01..b1d782ab 100755 --- a/scripts/cdist +++ b/scripts/cdist @@ -60,10 +60,9 @@ def commandline(): if __name__ == "__main__": - cdistpythonversion = '3.2' - if sys.version < cdistpythonversion: + if sys.version < cdist.MIN_SUPPORTED_PYTHON_VERSION: print('Python >= {} is required on the source host.'.format( - cdistpythonversion), file=sys.stderr) + cdist.MIN_SUPPORTED_PYTHON_VERSIO), file=sys.stderr) sys.exit(1) exit_code = 0