diff --git a/test/units/TEST-74-AUX-UTILS.machine-tags.sh b/test/units/TEST-74-AUX-UTILS.machine-tags.sh new file mode 100755 index 0000000000..5bd421e489 --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.machine-tags.sh @@ -0,0 +1,139 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC1091 +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +if ! command -v hostnamectl >/dev/null; then + echo "hostnamectl not found, skipping the test" + exit 0 +fi + +at_exit() { + set +e + + # Restore the original /etc/machine-info (if any) and make hostnamed read it again + if [[ -e /tmp/machine-info.bak ]]; then + mv /tmp/machine-info.bak /etc/machine-info + else + rm -f /etc/machine-info + fi + systemctl stop --job-mode=replace-irreversibly systemd-hostnamed.service + systemctl reset-failed systemd-hostnamed.service + + rm -fr "${ROOT:-}" +} + +trap at_exit EXIT + +# Read the TAGS= field out of /etc/machine-info (robust against the file being +# absent and against TAGS= being unset). +get_tags_from_file() ( + set +u + [[ -f /etc/machine-info ]] && . /etc/machine-info + echo "${TAGS:-}" +) + +if [[ -f /etc/machine-info ]]; then + cp /etc/machine-info /tmp/machine-info.bak +fi + +# Start from a clean slate, and make sure hostnamed re-reads the (now missing) file. +rm -f /etc/machine-info +systemctl stop --job-mode=replace-irreversibly systemd-hostnamed.service || : +systemctl reset-failed systemd-hostnamed.service || : + +# -------------------------------------------------------------------------------------------------- +# hostnamectl tags <-> /etc/machine-info +# -------------------------------------------------------------------------------------------------- + +# No tags are configured initially. +assert_eq "$(hostnamectl tags)" "" + +# Set some tags. Multiple arguments, colon-separated arguments, duplicates and arbitrary order are +# all accepted and normalized into a sorted, deduplicated, colon-separated list. +hostnamectl tags webserver:frontend frontend berlin +assert_eq "$(hostnamectl tags)" "berlin:frontend:webserver" + +# The very same list must show up in the TAGS= field of /etc/machine-info. +grep -qE '^TAGS="?berlin:frontend:webserver"?$' /etc/machine-info +assert_eq "$(get_tags_from_file)" "berlin:frontend:webserver" + +# Setting tags again replaces the previous list rather than extending it. +hostnamectl tags database +assert_eq "$(hostnamectl tags)" "database" +assert_eq "$(get_tags_from_file)" "database" + +# Invalid tags (only ASCII alphanumerics, '-' and '.' are allowed) are refused, leaving the +# previously configured tags untouched. +(! hostnamectl tags "invalid tag") +(! hostnamectl tags "invalid/tag") +assert_eq "$(hostnamectl tags)" "database" + +# Clearing all tags via a single empty string argument. When TAGS= was the only field, hostnamed +# removes /etc/machine-info altogether. +hostnamectl tags "" +assert_eq "$(hostnamectl tags)" "" +assert_eq "$(get_tags_from_file)" "" + +# -------------------------------------------------------------------------------------------------- +# ConditionMachineTag=/AssertMachineTag= in a transient unit +# -------------------------------------------------------------------------------------------------- + +hostnamectl tags alpha:beta:gamma +assert_eq "$(hostnamectl tags)" "alpha:beta:gamma" + +# When the condition matches, the unit is started and 'false' actually runs, so systemd-run fails. +# When the condition does not match, the unit is skipped (not failed) and systemd-run succeeds. +(! systemd-run --wait --pipe -p ConditionMachineTag=beta false) +systemd-run --wait --pipe -p ConditionMachineTag=delta false +# Globs are matched against each individual tag. +(! systemd-run --wait --pipe -p ConditionMachineTag='al*' false) +systemd-run --wait --pipe -p ConditionMachineTag='z*' false +# Negation inverts the result. +systemd-run --wait --pipe -p ConditionMachineTag='!beta' false +(! systemd-run --wait --pipe -p ConditionMachineTag='!delta' false) + +# Asserts behave like conditions, except a failing assert puts the unit into a failed state, which +# systemd-run propagates as a non-zero exit code. +systemd-run -p AssertMachineTag=beta -p Type=oneshot true +(! systemd-run -p AssertMachineTag=delta -p Type=oneshot true) + +# -------------------------------------------------------------------------------------------------- +# systemd-firstboot --machine-tags= and the firstboot.machine-tags credential +# -------------------------------------------------------------------------------------------------- + +if command -v systemd-firstboot >/dev/null; then + # The firstboot.machine-tags credential is split on ':', deduplicated and sorted, and written + # into /etc/machine-info underneath the target root. + ROOT="$(mktemp -d)" + systemd-run --wait --pipe --service-type=exec \ + -p SetCredential=firstboot.machine-tags:webserver:frontend:webserver:berlin \ + systemd-firstboot --root="$ROOT" + grep -qE '^TAGS="?berlin:frontend:webserver"?$' "$ROOT/etc/machine-info" + + # An invalid tag anywhere in the credential causes the whole list to be ignored, so no + # machine-info file is written. + rm -fr "$ROOT" + ROOT="$(mktemp -d)" + systemd-run --wait --pipe --service-type=exec \ + -p SetCredential=firstboot.machine-tags:'good:bad/tag' \ + systemd-firstboot --root="$ROOT" + test ! -e "$ROOT/etc/machine-info" + + # The --machine-tags= switch is normalized the same way and takes precedence over the credential. + rm -fr "$ROOT" + ROOT="$(mktemp -d)" + systemd-run --wait --pipe --service-type=exec \ + -p SetCredential=firstboot.machine-tags:ignored \ + systemd-firstboot --root="$ROOT" --machine-tags=database:cache:database + grep -qE '^TAGS="?cache:database"?$' "$ROOT/etc/machine-info" + + # An invalid tag passed on the command line is a hard error. + rm -fr "$ROOT" + ROOT="$(mktemp -d)" + (! systemd-firstboot --root="$ROOT" --machine-tags='good:bad/tag') +fi