</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>
<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>
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)
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)
#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"
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;
[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,
[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",
[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",
CONDITION_ENVIRONMENT,
CONDITION_CPU_FEATURE,
CONDITION_OS_RELEASE,
+ CONDITION_MACHINE_TAG,
CONDITION_MEMORY_PRESSURE,
CONDITION_CPU_PRESSURE,
CONDITION_IO_PRESSURE,
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;
AssertIOPressure=
AssertKernelCommandLine=
AssertKernelVersion=
+AssertMachineTag=
AssertMemoryPressure=
AssertNeedsUpdate=
AssertOSRelease=
ConditionIOPressure=
ConditionKernelCommandLine=
ConditionKernelVersion=
+ConditionMachineTag=
ConditionMemoryPressure=
ConditionNeedsUpdate=
ConditionOSRelease=