From: Lennart Poettering Date: Wed, 20 May 2026 21:23:15 +0000 (+0200) Subject: condition: add a condition that matches against the machine tags X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=461ec6facc4291cbdb3264d473bcab3e1d88e13a;p=thirdparty%2Fsystemd.git condition: add a condition that matches against the machine tags --- diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index 5c3439d193b..2990e658911 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -2058,6 +2058,25 @@ + + ConditionMachineTag= + + ConditionMachineTag= may be used to match against the tags + assigned to the local machine. Machine tags are short labels that classify and group machines for + management purposes; they are configured in the TAGS= field of + machine-info5 and + may be queried and changed with the tags command of + hostnamectl1. The + argument is a single tag pattern, which is compared against each of the configured tags using + shell-style globbing (*, ?, []). The + condition is satisfied if at least one of the configured tags matches the pattern. The test may be + negated by prepending an exclamation mark, in which case it is satisfied if none of the configured + tags matches. + + + + + ConditionMemoryPressure= ConditionCPUPressure= @@ -2126,6 +2145,7 @@ AssertCPUs= AssertCPUFeature= AssertOSRelease= + AssertMachineTag= AssertMemoryPressure= AssertCPUPressure= AssertIOPressure= diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index be074230ce5..60fec56f46a 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -398,6 +398,7 @@ Unit.ConditionUser, config_parse_unit_condition_string Unit.ConditionGroup, config_parse_unit_condition_string, CONDITION_GROUP, offsetof(Unit, conditions) Unit.ConditionControlGroupController, config_parse_unit_condition_string, CONDITION_CONTROL_GROUP_CONTROLLER, offsetof(Unit, conditions) Unit.ConditionOSRelease, config_parse_unit_condition_string, CONDITION_OS_RELEASE, offsetof(Unit, conditions) +Unit.ConditionMachineTag, config_parse_unit_condition_string, CONDITION_MACHINE_TAG, offsetof(Unit, conditions) Unit.ConditionMemoryPressure, config_parse_unit_condition_string, CONDITION_MEMORY_PRESSURE, offsetof(Unit, conditions) Unit.ConditionCPUPressure, config_parse_unit_condition_string, CONDITION_CPU_PRESSURE, offsetof(Unit, conditions) Unit.ConditionIOPressure, config_parse_unit_condition_string, CONDITION_IO_PRESSURE, offsetof(Unit, conditions) @@ -434,6 +435,7 @@ Unit.AssertUser, config_parse_unit_condition_string Unit.AssertGroup, config_parse_unit_condition_string, CONDITION_GROUP, offsetof(Unit, asserts) Unit.AssertControlGroupController, config_parse_unit_condition_string, CONDITION_CONTROL_GROUP_CONTROLLER, offsetof(Unit, asserts) Unit.AssertOSRelease, config_parse_unit_condition_string, CONDITION_OS_RELEASE, offsetof(Unit, asserts) +Unit.AssertMachineTag, config_parse_unit_condition_string, CONDITION_MACHINE_TAG, offsetof(Unit, asserts) Unit.AssertMemoryPressure, config_parse_unit_condition_string, CONDITION_MEMORY_PRESSURE, offsetof(Unit, asserts) Unit.AssertCPUPressure, config_parse_unit_condition_string, CONDITION_CPU_PRESSURE, offsetof(Unit, asserts) Unit.AssertIOPressure, config_parse_unit_condition_string, CONDITION_IO_PRESSURE, offsetof(Unit, asserts) diff --git a/src/shared/condition.c b/src/shared/condition.c index d0e41bbf716..ae33c18ade0 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -35,6 +35,7 @@ #include "glob-util.h" #include "hmac.h" #include "hostname-setup.h" +#include "hostname-util.h" #include "id128-util.h" #include "ima-util.h" #include "initrd-util.h" @@ -316,6 +317,37 @@ static int condition_test_osrelease(Condition *c, char **env) { return true; } +static int condition_test_machine_tag(Condition *c, char **env) { + int r; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_MACHINE_TAG); + + _cleanup_free_ char *tags = NULL; + r = parse_env_file( + /* f= */ NULL, etc_machine_info(), + "TAGS", &tags); + if (r < 0 && r != -ENOENT) { + log_debug_errno(r, "Failed to read /etc/machine-info, ignoring: %m"); + return false; + } + + /* Silently ignore invalid tags, matching the Tags D-Bus property */ + _cleanup_strv_free_ char **l = NULL; + r = machine_tags_from_string(tags, /* graceful= */ true, &l); + if (r < 0) { + log_debug_errno(r, "Failed to parse machine tags '%s', ignoring: %m", tags); + return false; + } + + STRV_FOREACH(i, l) + if (fnmatch(c->parameter, *i, /* flags= */ 0) == 0) + return true; + + return false; +} + static int condition_test_memory(Condition *c, char **env) { CompareOperator operator; uint64_t m, k; @@ -1344,6 +1376,7 @@ int condition_test(Condition *c, char **env) { [CONDITION_ENVIRONMENT] = condition_test_environment, [CONDITION_CPU_FEATURE] = condition_test_cpufeature, [CONDITION_OS_RELEASE] = condition_test_osrelease, + [CONDITION_MACHINE_TAG] = condition_test_machine_tag, [CONDITION_MEMORY_PRESSURE] = condition_test_psi, [CONDITION_CPU_PRESSURE] = condition_test_psi, [CONDITION_IO_PRESSURE] = condition_test_psi, @@ -1471,6 +1504,7 @@ static const char* const _condition_type_table[_CONDITION_TYPE_MAX] = { [CONDITION_ENVIRONMENT] = "ConditionEnvironment", [CONDITION_CPU_FEATURE] = "ConditionCPUFeature", [CONDITION_OS_RELEASE] = "ConditionOSRelease", + [CONDITION_MACHINE_TAG] = "ConditionMachineTag", [CONDITION_MEMORY_PRESSURE] = "ConditionMemoryPressure", [CONDITION_CPU_PRESSURE] = "ConditionCPUPressure", [CONDITION_IO_PRESSURE] = "ConditionIOPressure", @@ -1528,6 +1562,7 @@ static const char* const _assert_type_table[_CONDITION_TYPE_MAX] = { [CONDITION_ENVIRONMENT] = "AssertEnvironment", [CONDITION_CPU_FEATURE] = "AssertCPUFeature", [CONDITION_OS_RELEASE] = "AssertOSRelease", + [CONDITION_MACHINE_TAG] = "AssertMachineTag", [CONDITION_MEMORY_PRESSURE] = "AssertMemoryPressure", [CONDITION_CPU_PRESSURE] = "AssertCPUPressure", [CONDITION_IO_PRESSURE] = "AssertIOPressure", diff --git a/src/shared/condition.h b/src/shared/condition.h index 91af2027df6..1f5c33284c3 100644 --- a/src/shared/condition.h +++ b/src/shared/condition.h @@ -21,6 +21,7 @@ typedef enum ConditionType { CONDITION_ENVIRONMENT, CONDITION_CPU_FEATURE, CONDITION_OS_RELEASE, + CONDITION_MACHINE_TAG, CONDITION_MEMORY_PRESSURE, CONDITION_CPU_PRESSURE, CONDITION_IO_PRESSURE, diff --git a/src/test/test-condition.c b/src/test/test-condition.c index bee1014c1cb..4f450ab12d8 100644 --- a/src/test/test-condition.c +++ b/src/test/test-condition.c @@ -1414,6 +1414,69 @@ TEST(condition_test_os_release) { condition_free(condition); } +TEST(condition_test_machine_tag) { + Condition *condition; + + /* etc_machine_info() caches the path on first use, so redirect it before anything reads it and + * rewrite the same file (truncating) for each scenario rather than switching paths. */ + _cleanup_free_ char *saved = NULL; + ASSERT_OK(free_and_strdup(&saved, getenv("SYSTEMD_ETC_MACHINE_INFO"))); + + _cleanup_(rm_rf_physical_and_freep) char *d = NULL; + ASSERT_OK(mkdtemp_malloc(NULL, &d)); + + _cleanup_free_ char *f = path_join(d, "machine-info"); + ASSERT_NOT_NULL(f); + ASSERT_OK_ERRNO(setenv("SYSTEMD_ETC_MACHINE_INFO", f, /* overwrite= */ true)); + + ASSERT_OK(write_string_file(f, "TAGS=\"webserver:frontend:berlin\"\n", + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE)); + + /* Exact match */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "webserver", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* Glob match */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "front*", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* No match */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "database", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + /* Negation matches when the tag is absent, ... */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "database", /* trigger= */ false, /* negate= */ true))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* ... and does not match when the tag is present */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "webserver", /* trigger= */ false, /* negate= */ true))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + /* Invalid tags in the file are ignored (matching the Tags D-Bus property) */ + ASSERT_OK(write_string_file(f, "TAGS=\"in valid:good\"\n", + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE)); + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "in valid", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "good", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* No tags configured at all → never matches */ + ASSERT_OK(write_string_file(f, "PRETTY_HOSTNAME=\"x\"\n", + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE)); + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "webserver", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + ASSERT_OK(set_unset_env("SYSTEMD_ETC_MACHINE_INFO", saved, /* overwrite= */ true)); +} + TEST(condition_test_psi) { Condition *condition; CGroupMask mask; diff --git a/test/fuzz/fuzz-unit-file/directives-all.service b/test/fuzz/fuzz-unit-file/directives-all.service index 9fedef5a63a..0d6fadcc967 100644 --- a/test/fuzz/fuzz-unit-file/directives-all.service +++ b/test/fuzz/fuzz-unit-file/directives-all.service @@ -24,6 +24,7 @@ AssertHost= AssertIOPressure= AssertKernelCommandLine= AssertKernelVersion= +AssertMachineTag= AssertMemoryPressure= AssertNeedsUpdate= AssertOSRelease= @@ -72,6 +73,7 @@ ConditionHost= ConditionIOPressure= ConditionKernelCommandLine= ConditionKernelVersion= +ConditionMachineTag= ConditionMemoryPressure= ConditionNeedsUpdate= ConditionOSRelease=