condition: add a condition that matches against the machine tags

This commit is contained in:
Lennart Poettering
2026-05-21 18:30:16 +02:00
parent 71eac88e5f
commit 461ec6facc
6 changed files with 123 additions and 0 deletions
+20
View File
@@ -2058,6 +2058,25 @@
</listitem>
</varlistentry>
<varlistentry>
<term><varname>ConditionMachineTag=</varname></term>
<listitem><para><varname>ConditionMachineTag=</varname> 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 <varname>TAGS=</varname> field of
<citerefentry><refentrytitle>machine-info</refentrytitle><manvolnum>5</manvolnum></citerefentry> and
may be queried and changed with the <command>tags</command> command of
<citerefentry><refentrytitle>hostnamectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>. The
argument is a single tag pattern, which is compared against each of the configured tags using
shell-style globbing (<literal>*</literal>, <literal>?</literal>, <literal>[]</literal>). 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.</para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>ConditionMemoryPressure=</varname></term>
<term><varname>ConditionCPUPressure=</varname></term>
@@ -2126,6 +2145,7 @@
<term><varname>AssertCPUs=</varname></term>
<term><varname>AssertCPUFeature=</varname></term>
<term><varname>AssertOSRelease=</varname></term>
<term><varname>AssertMachineTag=</varname></term>
<term><varname>AssertMemoryPressure=</varname></term>
<term><varname>AssertCPUPressure=</varname></term>
<term><varname>AssertIOPressure=</varname></term>
+2
View File
@@ -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)
+35
View File
@@ -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",
+1
View File
@@ -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,
+63
View File
@@ -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;
@@ -24,6 +24,7 @@ AssertHost=
AssertIOPressure=
AssertKernelCommandLine=
AssertKernelVersion=
AssertMachineTag=
AssertMemoryPressure=
AssertNeedsUpdate=
AssertOSRelease=
@@ -72,6 +73,7 @@ ConditionHost=
ConditionIOPressure=
ConditionKernelCommandLine=
ConditionKernelVersion=
ConditionMachineTag=
ConditionMemoryPressure=
ConditionNeedsUpdate=
ConditionOSRelease=