diff --git a/man/hostnamectl.xml b/man/hostnamectl.xml
index 8ac18349c6..878837bee1 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 0baffeed9c..a4ebe25912 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 76473937a0..30facfb134 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 cc074967b5..d52580fbf5 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;