<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>
#include "polkit-agent.h"
#include "runtime-scope.h"
#include "string-util.h"
+#include "strv.h"
#include "time-util.h"
#include "verbs.h"
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;
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",
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,
{ "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) },
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;