]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
hostnamectl: add support for tagging the machine
authorLennart Poettering <lennart@amutable.com>
Wed, 20 May 2026 16:44:45 +0000 (18:44 +0200)
committerLennart Poettering <lennart@amutable.com>
Thu, 21 May 2026 16:30:16 +0000 (18:30 +0200)
man/hostnamectl.xml
shell-completion/bash/hostnamectl
shell-completion/zsh/_hostnamectl
src/hostname/hostnamectl.c

index 8ac18349c6718638ef8eca668c942eebc86d7d5b..878837bee1a1c63e17b937bd079ac5dea6c2411c 100644 (file)
         <xi:include href="version-info.xml" xpointer="v249"/>
         </listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><command>tags</command> [<replaceable>TAG</replaceable>…]</term>
+
+        <listitem><para>If no argument is given, print the machine tags currently configured, as a
+        colon-separated list. If one or more <replaceable>TAG</replaceable> 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.</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
+        must be 1…255 characters long and consist only of ASCII alphanumeric characters,
+        <literal>-</literal> and <literal>.</literal>. The tags are stored in the <varname>TAGS=</varname>
+        field of <filename>/etc/machine-info</filename>; see
+        <citerefentry><refentrytitle>machine-info</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+        details. They may also be matched against with the
+        <varname>ConditionMachineTag=</varname>/<varname>AssertMachineTag=</varname> unit settings, see
+        <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+
+        <xi:include href="version-info.xml" xpointer="v261"/>
+        </listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
index 0baffeed9c14addbafd8f8556d01966738e39f20..a4ebe2591277ee997de3c8d85e979beadbbaed76 100644 (file)
@@ -67,7 +67,7 @@ _hostnamectl() {
     local -A VERBS=(
         [STANDALONE]='status'
         [ICONS]='icon-name'
-        [NAME]='hostname deployment location'
+        [NAME]='hostname deployment location tags'
         [CHASSIS]='chassis'
     )
 
index 76473937a0040e9ee74d810131880bf8f180829f..30facfb134c39e57ceff71d759bd2fc8e327f58f 100644 (file)
@@ -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 "$@"
index cc074967b59156ffc5929d9b98c5eca0f72734a0..d52580fbf585b8b7ffbe0641c6ce5c340c588c34 100644 (file)
@@ -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;