hostnamectl: allow incrementally adding/removing tags

Teach the "tags" verb to accept arguments prefixed with '+' or '-': a
'+'-prefixed argument adds all of its (colon-separated) tags to the
configured list, a '-'-prefixed one removes them, mapping onto the new
AddAndRemoveTags() D-Bus method. The prefix applies to the whole
argument, so e.g.

    hostnamectl tags +foo:bar -quux:ooo

adds foo and bar and removes quux and ooo. Plain (replace) and prefixed
(add/remove) arguments may not be mixed in one invocation.
This commit is contained in:
Lennart Poettering
2026-06-01 10:56:16 +02:00
parent 8b5d45278b
commit 775c721328
2 changed files with 68 additions and 10 deletions
+8
View File
@@ -177,6 +177,14 @@
resulting list is sorted before being stored. To remove all tags, invoke the command with a single
empty string argument.</para>
<para>Alternatively, each argument may be prefixed with <literal>+</literal> or <literal>-</literal>
to incrementally update the configured tags rather than replacing them: an argument prefixed with
<literal>+</literal> adds all of its (colon-separated) tags to the list, one prefixed with
<literal>-</literal> removes them. For example, <command>hostnamectl tags +foo:bar -quux:ooo</command>
adds the tags <literal>foo</literal> and <literal>bar</literal> and removes <literal>quux</literal>
and <literal>ooo</literal>. The prefix applies to the whole argument, i.e. to every tag listed within
it. Prefixed and unprefixed arguments may not be combined in a single invocation.</para>
<para>Machine tags are short labels that may be used to classify and group machines for management
purposes, for example to identify the role a machine plays in a deployment, the fleet or
organizational unit it belongs to, or any other administrator-defined attribute. Each individual tag
+60 -10
View File
@@ -772,25 +772,75 @@ static int verb_get_or_set_tags(int argc, char *argv[], uintptr_t _data, void *u
return 0;
}
_cleanup_strv_free_ char **l = NULL;
_cleanup_strv_free_ char **plain = NULL, **add = NULL, **remove = NULL;
bool prefixed = false, unprefixed = false;
for (int i = 1; i < argc; i++) {
r = strv_split_and_extend(&l, argv[i], ":", /* filter_duplicates= */ true);
if (r < 0)
const char *e = argv[i];
char ***dest;
/* The first character of each argument selects the mode for all (colon-separated) tags in
* that argument: a leading '+' adds them, a leading '-' removes them, and otherwise the
* argument lists tags to set verbatim. Setting and adding/removing may not be mixed. */
if (e[0] == '+') {
dest = &add;
e++;
prefixed = true;
} else if (e[0] == '-') {
dest = &remove;
e++;
prefixed = true;
} else {
dest = &plain;
unprefixed = true;
}
if (isempty(e)) {
if (dest == &plain && !*dest) {
*dest = new0(char*, 1);
if (!*dest)
return log_oom();
}
} else if (strv_split_and_extend(dest, e, ":", /* filter_duplicates= */ true) < 0)
return log_oom();
}
strv_sort(l);
if (prefixed && unprefixed)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Plain tags may not be combined with '+'/'-' prefixed tags.");
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
r = bus_message_new_method_call(bus, &m, bus_hostname, "SetTags");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, l);
if (r < 0)
return bus_log_create_error(r);
if (!prefixed) {
/* No prefixes used: replace the whole tag list with the given tags (possibly empty, which
* clears all tags). */
strv_sort(plain);
r = bus_message_new_method_call(bus, &m, bus_hostname, "SetTags");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, plain);
if (r < 0)
return bus_log_create_error(r);
} else {
/* Prefixes used: incrementally add and/or remove tags. */
strv_sort(add);
strv_sort(remove);
r = bus_message_new_method_call(bus, &m, bus_hostname, "AddAndRemoveTags");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, add);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, remove);
if (r < 0)
return bus_log_create_error(r);
}
r = sd_bus_call(bus, m, /* usec= */ 0, &error, /* ret_reply= */ NULL);
if (r < 0)