Compare commits

...
Sign in to create a new pull request.

35 commits

Author SHA1 Message Date
Darko Poljak
10ca1c12fd ++changelog 2021-03-12 08:21:03 +01:00
c55397766e Merge branch 'feature/type/__sshd_config/whitelist-openbmc' into 'master'
__sshd_config: Whitelist OpenBMC

See merge request ungleich-public/cdist!980
2021-03-12 08:20:35 +01:00
Dennis Camera
e47c4dd8a4 [type/__sshd_config] Whitelist OpenBMC in manifest 2021-03-11 14:17:44 +01:00
Darko Poljak
31cc592aa1 ++changelog 2021-03-10 19:25:04 +01:00
2f4a7e1a94 Merge branch 'fix/type/__ssh_authorized_key/grep-only-if-file-exists' into 'master'
__ssh_authorized_key: only grep if file exists

See merge request ungleich-public/cdist!979
2021-03-10 19:24:23 +01:00
Dennis Camera
fb19f34266 [type/__ssh_authorized_key] Only grep if file exists 2021-03-09 21:15:26 +01:00
Steven Armstrong
ecba284fc8 changelog++
Signed-off-by: Steven Armstrong <steven@icarus.ethz.ch>
2021-03-05 16:13:02 +01:00
Steven Armstrong
ea0126dd81 Make local state dir available to custom remote scripts
Signed-off-by: Steven Armstrong <steven@icarus.ethz.ch>
2021-03-05 16:11:49 +01:00
Darko Poljak
e7d33891df ++changelog 2021-03-02 09:29:33 +01:00
1bc0d912bf Merge branch 'fix/type/__pyvenv/man-typo' into 'master'
__pyvenv: Fix user example

See merge request ungleich-public/cdist!978
2021-03-02 09:28:50 +01:00
Dennis Camera
8ef19d47f6 [type/__pyvenv] Fix example (--user -> --owner) 2021-03-01 17:59:45 +01:00
Darko Poljak
60fd7ba1f3 Release 6.9.5 2021-02-28 13:37:23 +01:00
Darko Poljak
22f637c15b ++changelog 2021-02-23 06:29:24 +01:00
6358885d26 Merge branch 'feature/__package_pip/extras' into 'master'
__package_pip: add optional (extra) dependencies

See merge request ungleich-public/cdist!975
2021-02-23 06:27:09 +01:00
Darko Poljak
5e0572189f ++changelog 2021-02-22 09:11:22 +01:00
b3a9c907ad Merge branch '__letsencrypt_cert-fix-hooks' into 'master'
[__letsencrypt_cert] Fix various issues with hooks.

Closes #853

See merge request ungleich-public/cdist!977
2021-02-22 09:09:45 +01:00
e854db096e Merge branch 'fix/type/__postgres_role/implement-alter' into 'master'
__postgres_role: implement modification of roles

See merge request ungleich-public/cdist!973
2021-02-22 08:58:58 +01:00
d1f45d3524 __package_pip: corrected typo in man
.. by fully replacing it with a smaller sentence.
2021-02-19 09:03:56 +01:00
2ce1fce767 __package_pip: match package names case insensitive
Pip matches them insensitive, so we need to do the same to avoid
problems by saying extras are not installed but already is there in
place.
2021-02-15 16:17:46 +01:00
951712740f __package_pip: update man.rst
Adjusted comments for `explorer/extras` and updated the man page for the
new behaviour of updating the extras.
2021-02-12 13:42:51 +01:00
a9d7dfb2ed __package_pip: split extra 'all' to a list of all extras
This will fix if a package will be upgraded from some extras to all
extras. Previously, it will not work because some dependencies of 'all'
are already installed, so the feature 'all' is already installed.

Now, it will use a list of all extras to iterate over them separatly. This
will result it will never install all extras via `[all]`, but rather
`[foo,bar]`.
2021-02-12 09:17:02 +01:00
7398382890 __package_pip: fix shellcheck
Useless `cat $file`, use `< $file` instead.
2021-02-11 23:12:10 +01:00
2db0ef7c98 __package_pip: updating real detection of extras
As the previous detection took the wrong values, this explorer now
checks if packages for an extra are installed or not. If not, the extra
is not installed.

Based on the information of the explorer, it will install the package
again with the absent extras.
2021-02-11 22:53:26 +01:00
8dc6ab9738 __package_pip: install not found extras
Compares the explorer against the parameters and install those extras
that are not already installed.
2021-02-11 13:49:53 +01:00
4717e5ceff __package_pip: add extras explorer
The two new explorers detect all installed extras for this package.
2021-02-11 10:31:07 +01:00
73a03d75d7 __package_pip: fix shellcheck 2021-02-04 19:18:02 +01:00
8eccacec59 __package_pip: add optional dependencies
This is a poor implementation of optional dependencies for pip packages.
It ensures to install them if the package will be installed, but does
not take into account if they must be added/removed after the package is
already installed. Also, it will not be autoremoved, as all dependencies
will not be removed.
2021-02-04 19:09:26 +01:00
Dennis Camera
35cde3e666 [type/__postgres_role] Fix state explorer when stored password is empty 2021-01-18 13:09:29 +01:00
Dennis Camera
2954347771 [type/__postgres_role] Add note regarding empty passwords 2021-01-14 13:46:40 +01:00
Dennis Camera
99d82fd0d5 [type/__postgres_role] Always set psql -q 2020-12-17 17:05:58 +01:00
Dennis Camera
1180f13ed6 [type/__postgres_role] Fix setting password
We need to make sure that the password does not end up in ~/.psql_history.
2020-12-17 17:03:58 +01:00
Dennis Camera
4859c27900 [type/__postgres_role] Refactor gencode-remote 2020-12-17 16:57:43 +01:00
Dennis Camera
7b7ca4d385 [type/__postgres_role] Handle password changes 2020-12-16 19:07:05 +01:00
Dennis Camera
c36df82882 [type/__postgres_role] ALTER ROLE when parameters change 2020-12-15 21:11:48 +01:00
Dennis Camera
932e2496ed [type/__postgres_role] Lint 2020-12-15 18:40:39 +01:00
14 changed files with 398 additions and 63 deletions

View file

@ -0,0 +1,45 @@
#!/bin/sh
#
# 2021 Matthias Stecher (matthiasstecher at gmx.de)
#
# 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 <http://www.gnu.org/licenses/>.
#
nameparam="$__object/parameter/name"
if [ -f "$nameparam" ]; then
name=$(cat "$nameparam")
else
name="$__object_id"
fi
pipparam="$__object/parameter/pip"
if [ -f "$pipparam" ]; then
pip=$(cat "$pipparam")
else
pip="$( "$__type_explorer/pip" )"
fi
if command -v "$pip" >/dev/null 2>&1; then
# assemble the path where pip stores all pip package info
"$pip" show "$name" \
| awk -F': ' '
$1 == "Name" {name=$2; gsub(/-/,"_",name); next}
$1 == "Version" {version=$2; next}
$1 == "Location" {location=$2; next}
END {if (version != "") printf "%s/%s-%s.dist-info", location, name, version}'
fi

View file

@ -0,0 +1,66 @@
#!/bin/sh
#
# 2021 Matthias Stecher (matthiasstecher at gmx.de)
#
# 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 <http://www.gnu.org/licenses/>.
#
#
# Checks if the given extras are really installed or not. It will be
# done by querring all dependencies for that extra and return it as
# "to be installed" if no dependency was found.
#
distinfo_dir="$("$__type_explorer/distinfo-dir")"
# check if we have something to check
if [ "$distinfo_dir" ] && [ -s "$__object/parameter/extra" ]
then
# save cause freezing is slow
mkdir "$__object/files"
pip_freeze="$__object/files/pip-freeze.tmp"
pip3 freeze > "$pip_freeze"
# If all is set, it searches all available extras to separatly check them.
# It would work with just 'all' (cause dependencies are specified for
# 'all'), but will not update if one extra is already present. Side effect
# is that it will not use [all] but instead name all extras seperatly.
for extra in $(if grep -qFx all "$__object/parameter/extra";
then awk -F': ' '$1 == "Provides-Extra" && $2 != "all"{print $2}' "$distinfo_dir/METADATA";
else tr ',' '\n' < "$__object/parameter/extra";
fi)
do
# create a grep BRE pattern to search all packages
# maybe a file full of patterns for -F could be written
grep_pattern="$(
awk -F'(: | ; )' -v check="$extra" '
$1 == "Requires-Dist" {
split($2, r, " ");
sub("extra == ", "", $3); gsub("'"'"'", "", $3);
if($3 == check) print r[1]
}' "$distinfo_dir/METADATA" \
| sed ':a; $!N; s/\n/\\|/; ta'
)"
# echo the extra if no packages where found for it
# if there is no pattern, we don't need to search ;-)
# pip matches packages case-insensetive, we need to do that, too
if [ "$grep_pattern" ] && ! grep -qi "$grep_pattern" "$pip_freeze"
then
echo "$extra"
fi
done
fi

0
cdist/conf/type/__package_pip/explorer/state Normal file → Executable file
View file

View file

@ -2,6 +2,7 @@
#
# 2012 Nico Schottelius (nico-cdist at schottelius.org)
# 2016 Darko Poljak (darko.poljak at gmail.com)
# 2021 Matthias Stecher (matthiasstecher at gmx.de)
#
# This file is part of cdist.
#
@ -25,7 +26,10 @@
state_is=$(cat "$__object/explorer/state")
state_should="$(cat "$__object/parameter/state")"
[ "$state_is" = "$state_should" ] && exit 0
# short circuit if state is the same and no extras to install
[ "$state_is" = "$state_should" ] && ! [ -s "$__object/explorer/extras" ] \
&& exit 0
nameparam="$__object/parameter/name"
if [ -f "$nameparam" ]; then
@ -56,6 +60,14 @@ fi
case "$state_should" in
present)
if [ -s "$__object/explorer/extras" ]
then
# all extras are passed to pip in a comma-separated list in the name
# sed loops through all input lines and add commas between them
extras="$(sed ':a; $!N; s/\n/,/; ta' "$__object/explorer/extras")"
name="${name}[${extras}]"
fi
if [ "$runas" ]
then
echo "su -c '$pip install -q $name' $runas"

View file

@ -22,6 +22,16 @@ OPTIONAL PARAMETERS
name
If supplied, use the name and not the object id as the package name.
extra
Extra optional dependencies which should be installed along the selected
package. Can be specified multiple times. Multiple extras can be passed
in one `--extra` as a comma-separated list.
Extra optional dependencies will be installed even when the base package
is already installed. Notice that the type will not remove installed extras
that are not explicitly named for the type because pip does not offer a
management for orphaned packages and they may be used by other packages.
pip
Instead of using pip from PATH, use the specific pip path.
@ -46,6 +56,14 @@ EXAMPLES
# Use pip in a virtualenv located at /foo/shinken_virtualenv as user foo
__package_pip pyro --state present --pip /foo/shinken_virtualenv/bin/pip --runas foo
# Install package with optional dependencies
__package_pip mautrix-telegram --extra speedups --extra webp_convert --extra hq_thumbnails
# the extras can also be specified comma-separated
__package_pip mautrix-telegram --extra speedups,webp_convert,hq_thumbnails --extra postgres
# or take all extras
__package_pip mautrix-telegram --extra all
SEE ALSO
--------
@ -54,12 +72,13 @@ SEE ALSO
AUTHORS
-------
Nico Schottelius <nico-cdist--@--schottelius.org>
| Nico Schottelius <nico-cdist--@--schottelius.org>
| Matthias Stecher <matthiasstecher--@--gmx.de>
COPYING
-------
Copyright \(C) 2012 Nico Schottelius. 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.
Copyright \(C) 2012 Nico Schottelius, 2021 Matthias Stecher. 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.

View file

@ -0,0 +1 @@
extra

View file

@ -1,6 +1,7 @@
#!/bin/sh
#!/bin/sh -e
#
# 2011 Steven Armstrong (steven-cdist at armstrong.cc)
# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch)
#
# This file is part of cdist.
#
@ -11,32 +12,140 @@
#
# 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
# 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 <http://www.gnu.org/licenses/>.
#
case "$("${__explorer}/os")"
case $("${__explorer:?}/os")
in
netbsd)
postgres_user='pgsql'
;;
openbsd)
postgres_user='_postgresql'
;;
*)
postgres_user='postgres'
;;
(netbsd)
postgres_user='pgsql'
;;
(openbsd)
postgres_user='_postgresql'
;;
(*)
postgres_user='postgres'
;;
esac
rolename=${__object_id:?}
name="$__object_id"
if test -n "$(su - "$postgres_user" -c "psql postgres -twAc \"SELECT 1 FROM pg_roles WHERE rolname='$name'\"")"
psql_query() {
su -l "${postgres_user}" -c "$(
printf "psql -q -F '\034' -R '\036' -wAc '%s'" \
"$(printf %s "$*" | sed "s/'/'\\\\''/g")"
)"
}
password_check_login() (
PGPASSWORD=$(cat "${__object:?}/parameter/password"; printf .)
PGPASSWORD=${PGPASSWORD%?.}
export PGPASSWORD
psql -q -w -h localhost -U "${rolename}" template1 -c '\q' >/dev/null 2>&1
)
role_properties=$(
psql_query "SELECT * FROM pg_roles WHERE rolname = '${rolename}'" \
| awk '
BEGIN { RS = "\036"; FS = "\034" }
/^\([0-9]+ rows?\)/ { exit }
NR == 1 { for (i = 1; i <= NF; i++) cols[i] = $i; next }
NR == 2 { for (i = 1; i <= NF; i++) printf "%s=%s\n", cols[i], $i }
'
)
if test -n "${role_properties}"
then
echo 'present'
# Check if the user's properties match the parameters
for prop in login createdb createrole superuser
do
bool_should=$(test -f "${__object:?}/parameter/${prop}" && echo 't' || echo 'f')
bool_is=$(
printf '%s\n' "${role_properties}" |
awk -F '=' -v key="${prop}" '
BEGIN {
if (key == "login")
key = "canlogin"
else if (key == "superuser")
key = "super"
key = "rol" key
}
$1 == key {
sub(/^[^=]*=/, "")
print
}
'
)
test "${bool_is}" = "${bool_should}" || {
state='different properties'
}
done
# Check password
passwd_stored=$(
psql_query "SELECT rolpassword FROM pg_authid WHERE rolname = '${rolename}'" \
| awk 'BEGIN { RS = "\036" } NR == 2'
printf .
)
passwd_stored=${passwd_stored%?.}
if test -f "${__object:?}/parameter/password"
then
passwd_should=$(cat "${__object:?}/parameter/password"; printf .)
fi
passwd_should=${passwd_should%?.}
if test -z "${passwd_stored}"
then
test -z "${passwd_should}" || state="${state:-different} password"
elif expr "${passwd_stored}" : 'SCRAM-SHA-256\$.*$' >/dev/null
then
# SCRAM-SHA-256 "encrypted" password
# NOTE: There is currently no easy way to check SCRAM passwords without
# logging in
password_check_login || state="${state:-different} password"
elif expr "${passwd_stored}" : 'md5[0-9a-f]\{32\}$' >/dev/null
then
# MD5 "encrypted" password
if command -v md5sum >/dev/null 2>&1
then
should_md5=$(
printf '%s%s' "${passwd_should}" "${rolename}" \
| md5sum - | sed -e 's/[^0-9a-f]*$//')
elif command -v gmd5sum >/dev/null 2>&1
then
should_md5=$(
printf '%s%s' "${passwd_should}" "${rolename}" \
| gmd5sum - | sed -e 's/[^0-9a-f]*$//')
elif command -v openssl >/dev/null 2>&1
then
should_md5=$(
printf '%s%s' "${passwd_should}" "${rolename}" \
| openssl dgst -md5 | sed 's/^.* //')
fi
if test -n "${should_md5}"
then
test "${passwd_stored}" = "md5${should_md5}" \
|| state="${state:-different} password"
else
password_check_login || state="${state:-different} password"
fi
else
# unencrypted password (unsupported since PostgreSQL 10)
test "${passwd_stored}" = "${passwd_should}" \
|| state="${state:-different} password"
fi
test -n "${state}" || state='present'
else
echo 'absent'
state='absent'
fi
echo "${state}"

View file

@ -1,6 +1,7 @@
#!/bin/sh -e
#
# 2011 Steven Armstrong (steven-cdist at armstrong.cc)
# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch)
#
# This file is part of cdist.
#
@ -11,55 +12,117 @@
#
# 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
# 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 <http://www.gnu.org/licenses/>.
#
case "$(cat "${__global}/explorer/os")"
quote() {
if test $# -gt 0
then
printf '%s' "$*"
else
cat -
fi | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/"
}
case $(cat "${__global:?}/explorer/os")
in
netbsd)
postgres_user='pgsql'
;;
openbsd)
postgres_user='_postgresql'
;;
*)
postgres_user='postgres'
;;
(netbsd)
postgres_user='pgsql'
;;
(openbsd)
postgres_user='_postgresql'
;;
(*)
postgres_user='postgres'
;;
esac
name="$__object_id"
state_is="$(cat "$__object/explorer/state")"
state_should="$(cat "$__object/parameter/state")"
rolename=${__object_id:?}
state_is=$(cat "${__object:?}/explorer/state")
state_should=$(cat "${__object:?}/parameter/state")
[ "$state_is" = "$state_should" ] && exit 0
if test "${state_is}" = "${state_should}"
then
exit 0
fi
case "$state_should" in
present)
if [ -f "$__object/parameter/password" ]; then
password="$(cat "$__object/parameter/password")"
fi
booleans=""
for boolean in login createdb createrole superuser; do
if [ ! -f "$__object/parameter/$boolean" ]; then
boolean="no${boolean}"
fi
upper=$(echo $boolean | tr '[:lower:]' '[:upper:]')
booleans="$booleans $upper"
done
psql_query() {
printf 'su -l %s -c %s\n' \
"$(quote "${postgres_user}")" \
"$(quote "psql postgres -q -w -c $(quote "$1")")"
}
[ -n "$password" ] && password="PASSWORD '$password'"
cat << EOF
su - '$postgres_user' -c "psql postgres -wc \"CREATE ROLE \\\\\"$name\\\\\" WITH $password $booleans;\""
EOF
;;
absent)
cat << EOF
su - '$postgres_user' -c "dropuser \"$name\""
EOF
;;
psql_set_password() {
# NOTE: Always make sure that the password does not end up in psql_history!
# NOTE: Never set an empty string as the password, because they can be
# interpreted differently by different tooling.
if test -s "${__object:?}/parameter/password"
then
cat <<-EOF
exec 3< "\${__object:?}/parameter/password"
su -l '${postgres_user}' -c 'psql -q -w postgres' <<'SQL'
\set HISTFILE /dev/null
\set pw \`cat <&3\`
ALTER ROLE "${rolename}" WITH PASSWORD :'pw';
SQL
exec 3<&-
EOF
else
psql_query "ALTER ROLE \"${rolename}\" WITH PASSWORD NULL;"
fi
}
role_properties_should() {
_props=
for _prop in login createdb createrole superuser
do
_props="${_props}${_props:+ }$(
if test -f "${__object:?}/parameter/${_prop}"
then
echo "${_prop}"
else
echo "no${_prop}"
fi \
| tr '[:lower:]' '[:upper:]')"
done
printf '%s\n' "${_props}"
unset _prop _props
}
case ${state_should}
in
(present)
case ${state_is}
in
(absent)
psql_query "CREATE ROLE \"${rolename}\" WITH $(role_properties_should);"
psql_set_password
;;
(different*)
if expr "${state_is}" : 'different.*properties' >/dev/null
then
psql_query "ALTER ROLE \"${rolename}\" WITH $(role_properties_should);"
fi
if expr "${state_is}" : 'different.*password' >/dev/null
then
psql_set_password
fi
;;
(*)
printf 'Invalid state reported by state explorer: %s\n' "${state_is}" >&2
exit 1
;;
esac
;;
(absent)
printf 'su -l %s -c %s\n' \
"$(quote "${postgres_user}")" \
"$(quote "dropuser $(quote "${rolename}")")"
;;
esac

View file

@ -61,7 +61,7 @@ EXAMPLES
__pyvenv /home/foo/fooenv --pyvenv /usr/local/bin/pyvenv-3.4
# Create python virtualenv for user foo.
__pyvenv /home/foo/fooenv --group foo --user foo
__pyvenv /home/foo/fooenv --group foo --owner foo
# Create python virtualenv with specific parameters.
__pyvenv /home/services/djangoenv --venvparams "--copies --system-site-packages"

View file

@ -25,6 +25,7 @@ type_and_key="$(tr ' ' '\n' < "$__object/parameter/key"| awk '/^(ssh|ecdsa)-[^ ]
if [ -n "${type_and_key}" ]
then
file="$(cat "$__object/parameter/file")"
test -e "$file" || exit 0
# get any entries that match the type and key

View file

@ -37,9 +37,9 @@ tmpfile=\$(mktemp ${file}.cdist.XXXXXXXXXX)
# preserve ownership and permissions of existing file
if [ -f "$file" ]; then
cp -p "$file" "\$tmpfile"
grep -v -F -x '$line' '$file' >\$tmpfile
fi
grep -v -F -x '$line' '$file' > \$tmpfile || true
mv -f "\$tmpfile" "$file"
cat "\$tmpfile" >"$file"
DONE
}

View file

@ -39,7 +39,14 @@ in
(freebsd|netbsd|openbsd)
# whitelist
;;
(openbmc-phosphor)
# whitelist
# OpenBMC can be configured with dropbear and OpenSSH.
# If dropbear is used, the state explorer will already fail because it
# cannot find the sshd binary.
;;
(*)
: "${__type:?}" # make shellcheck happy
printf 'Your operating system (%s) is currently not supported by this type (%s)\n' \
"${os}" "${__type##*/}" >&2
printf 'Please contribute an implementation for it if you can.\n' >&2

View file

@ -420,6 +420,9 @@ class Config:
exec_path=sys.argv[0],
save_output_streams=args.save_output_streams)
# Make __global state dir available to custom remote scripts.
os.environ['__global'] = local.base_path
remote = cdist.exec.remote.Remote(
target_host=target_host,
remote_exec=remote_exec,

View file

@ -2,9 +2,18 @@ Changelog
---------
next:
* Type __pyvenv: Fix user example in man page (Dennis Camera)
* Core: config: Make local state directory available to custom remotes (Steven Armstrong
* Type __ssh_authorized_key: grep only if file exists (Dennis Camera)
* Type __sshd_config: Whitelist OpenBMC (Dennis Camera)
6.9.5: 2021-02-28
* Core: preos: Fix passing cdist debug parameter (Darko Poljak)
* Type __sshd_config: Produce error if invalid config is generated, fix processing of AuthenticationMethods and AuthorizedKeysFile, document explorer bug (Dennis Camera)
* Explorer memory: Fix result units; support Solaris (Dennis Camera)
* Type __postgres_role: Implement modification of roles (Dennis Camera)
* Type __letsencrypt_cert: Fix issues with hooks (Evil Ham)
* Type __package_pip: Add optional extra dependencies param (Matthias Stecher)
6.9.4: 2020-12-21
* Type __package_pkgng_freebsd: Fix bootstrapping pkg (Dennis Camera)