From: Lennart Poettering Date: Wed, 20 May 2026 16:44:45 +0000 (+0200) Subject: hostnamectl: add support for tagging the machine X-Git-Tag: v261-rc1~29^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=71eac88e5f69cd9432000dd69bd31da8cc34b3c0;p=thirdparty%2Fsystemd.git hostnamectl: add support for tagging the machine --- diff --git a/man/hostnamectl.xml b/man/hostnamectl.xml index 8ac18349c67..878837bee1a 100644 --- a/man/hostnamectl.xml +++ b/man/hostnamectl.xml @@ -165,6 +165,32 @@ + + + tags [TAG…] + + If no argument is given, print the machine tags currently configured, as a + colon-separated list. If one or more TAG arguments are provided then the + command replaces the configured tags with the specified ones. Each argument may itself be a + colon-separated list of tags, so that the tags may be specified either as multiple arguments or as a + single colon-separated argument, or any combination thereof. Duplicate tags are removed and the + resulting list is sorted before being stored. To remove all tags, invoke the command with a single + empty string argument. + + 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 + must be 1…255 characters long and consist only of ASCII alphanumeric characters, + - and .. The tags are stored in the TAGS= + field of /etc/machine-info; see + machine-info5 for + details. They may also be matched against with the + ConditionMachineTag=/AssertMachineTag= unit settings, see + systemd.unit5. + + + + diff --git a/shell-completion/bash/hostnamectl b/shell-completion/bash/hostnamectl index 0baffeed9c1..a4ebe259127 100644 --- a/shell-completion/bash/hostnamectl +++ b/shell-completion/bash/hostnamectl @@ -67,7 +67,7 @@ _hostnamectl() { local -A VERBS=( [STANDALONE]='status' [ICONS]='icon-name' - [NAME]='hostname deployment location' + [NAME]='hostname deployment location tags' [CHASSIS]='chassis' ) diff --git a/shell-completion/zsh/_hostnamectl b/shell-completion/zsh/_hostnamectl index 76473937a00..30facfb134c 100644 --- a/shell-completion/zsh/_hostnamectl +++ b/shell-completion/zsh/_hostnamectl @@ -47,6 +47,11 @@ _hostnamectl_location() { fi } +(( $+functions[_hostnamectl_tags] )) || +_hostnamectl_tags() { + _message "machine tag" +} + (( $+functions[_hostnamectl_commands] )) || _hostnamectl_commands() { local -a _hostnamectl_cmds @@ -57,6 +62,7 @@ _hostnamectl_commands() { "chassis:Get/set chassis type for host" "deployment:Get/set deployment environment for host" "location:Get/set location for host" + "tags:Get/set machine tags for host" ) if (( CURRENT == 1 )); then _describe -t commands 'hostnamectl commands' _hostnamectl_cmds || compadd "$@" diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index cc074967b59..d52580fbf58 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -30,6 +30,7 @@ #include "polkit-agent.h" #include "runtime-scope.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" #include "verbs.h" @@ -50,6 +51,7 @@ typedef struct StatusInfo { const char *chassis_asset_tag; const char *deployment; const char *location; + char **tags; const char *kernel_name; const char *kernel_release; const char *os_pretty_name; @@ -197,6 +199,18 @@ static int print_status_info(StatusInfo *i) { return table_log_add_error(r); } + if (!strv_isempty(i->tags)) { + _cleanup_free_ char *j = strv_join(i->tags, ":"); + if (!j) + return log_oom(); + + r = table_add_many(table, + TABLE_FIELD, "Tags", + TABLE_STRING, j); + if (r < 0) + return table_log_add_error(r); + } + if (!sd_id128_is_null(i->machine_id)) { r = table_add_many(table, TABLE_FIELD, "Machine ID", @@ -412,8 +426,14 @@ static int get_one_name(sd_bus *bus, const char* attr, char **ret) { return 0; } +static void status_info_done(StatusInfo *info) { + assert(info); + + info->tags = strv_free(info->tags); +} + static int show_all_names(sd_bus *bus) { - StatusInfo info = { + _cleanup_(status_info_done) StatusInfo info = { .vsock_cid = VMADDR_CID_ANY, .os_support_end = USEC_INFINITY, .firmware_date = USEC_INFINITY, @@ -428,6 +448,7 @@ static int show_all_names(sd_bus *bus) { { "ChassisAssetTag", "s", NULL, offsetof(StatusInfo, chassis_asset_tag)}, { "Deployment", "s", NULL, offsetof(StatusInfo, deployment) }, { "Location", "s", NULL, offsetof(StatusInfo, location) }, + { "Tags", "as", NULL, offsetof(StatusInfo, tags) }, { "KernelName", "s", NULL, offsetof(StatusInfo, kernel_name) }, { "KernelRelease", "s", NULL, offsetof(StatusInfo, kernel_release) }, { "OperatingSystemPrettyName", "s", NULL, offsetof(StatusInfo, os_pretty_name) }, @@ -720,6 +741,64 @@ static int verb_get_or_set_location(int argc, char *argv[], uintptr_t _data, voi set_simple_string(userdata, "location", "SetLocation", argv[1]); } +VERB(verb_get_or_set_tags, "tags", "[TAG …]", VERB_ANY, VERB_ANY, 0, "Get/set machine tags for host"); +static int verb_get_or_set_tags(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + if (argc == 1) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + + _cleanup_free_ char *j = NULL; + r = bus_get_property(bus, bus_hostname, "Tags", &error, &reply, "as"); + if (r < 0) { + if (!sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY)) + return log_error_errno(r, "Could not get property: %s", bus_error_message(&error, r)); + + /* Old hostnamed didn't know the tags concept, hence such a machine has no tags. */ + } else { + _cleanup_strv_free_ char **l = NULL; + r = sd_bus_message_read_strv(reply, &l); + if (r < 0) + return bus_log_parse_error(r); + + j = strv_join(l, ":"); + if (!j) + return log_oom(); + } + + printf("%s\n", strempty(j)); + return 0; + } + + _cleanup_strv_free_ char **l = NULL; + for (int i = 1; i < argc; i++) { + r = strv_split_and_extend(&l, argv[i], ":", /* filter_duplicates= */ true); + if (r < 0) + return log_oom(); + } + + strv_sort(l); + + (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); + + r = sd_bus_call(bus, m, /* usec= */ 0, &error, /* ret_reply= */ NULL); + if (r < 0) + return log_error_errno(r, "Could not set tags: %s", bus_error_message(&error, r)); + + return 0; +} + static int help(void) { _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r;