hostnamed: introduce "machine tags" concept
For management purposes it's useful to be able to "tag" a machine with various labels. Let's add a field for that to /etc/machine-info and make it settable. Fixes: #38591
This commit is contained in:
+23
-1
@@ -139,6 +139,27 @@
|
||||
<xi:include href="version-info.xml" xpointer="v216"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>TAGS=</varname></term>
|
||||
|
||||
<listitem><para>A colon-separated list of tags attached to this 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 (<literal>webserver</literal>, <literal>database</literal>), the
|
||||
fleet or organizational unit it belongs to, or any other administrator-defined attribute. Example:
|
||||
<literal>TAGS=webserver:frontend:berlin</literal>.</para>
|
||||
|
||||
<para>Each individual tag must be 1…255 characters long and may consist only of the ASCII
|
||||
alphanumeric characters, <literal>-</literal> and <literal>.</literal>.</para>
|
||||
|
||||
<para>The configured tags may be matched against with the
|
||||
<varname>ConditionMachineTag=</varname> and <varname>AssertMachineTag=</varname> unit settings, see
|
||||
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
for details. They may be queried and changed with the <command>tags</command> command of
|
||||
<citerefentry><refentrytitle>hostnamectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>HARDWARE_VENDOR=</varname></term>
|
||||
|
||||
@@ -195,7 +216,8 @@
|
||||
<programlisting>PRETTY_HOSTNAME="Lennart's Tablet"
|
||||
ICON_NAME=computer-tablet
|
||||
CHASSIS=tablet
|
||||
DEPLOYMENT=production</programlisting>
|
||||
DEPLOYMENT=production
|
||||
TAGS=demo:berlin</programlisting>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
|
||||
@@ -56,6 +56,7 @@ node /org/freedesktop/hostname1 {
|
||||
in b interactive);
|
||||
SetLocation(in s location,
|
||||
in b interactive);
|
||||
SetTags(in as tags);
|
||||
GetProductUUID(in b interactive,
|
||||
out ay uuid);
|
||||
GetHardwareSerial(out s serial);
|
||||
@@ -73,6 +74,7 @@ node /org/freedesktop/hostname1 {
|
||||
readonly s Chassis = '...';
|
||||
readonly s Deployment = '...';
|
||||
readonly s Location = '...';
|
||||
readonly as Tags = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly s KernelName = '...';
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
@@ -142,6 +144,8 @@ node /org/freedesktop/hostname1 {
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="SetLocation()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="SetTags()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="GetProductUUID()"/>
|
||||
|
||||
<variablelist class="dbus-method" generated="True" extra-ref="GetHardwareSerial()"/>
|
||||
@@ -168,6 +172,8 @@ node /org/freedesktop/hostname1 {
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="Location"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="Tags"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="KernelName"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="KernelRelease"/>
|
||||
@@ -268,6 +274,15 @@ node /org/freedesktop/hostname1 {
|
||||
configure the chassis type if it could not be auto-detected. Set this property to the empty string to
|
||||
reenable the automatic detection of the chassis type from firmware information.</para>
|
||||
|
||||
<para>The <varname>Tags</varname> property exposes a list of <emphasis>machine tags</emphasis>: 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 is 1…255 characters long and consists only of the
|
||||
ASCII alphanumeric characters, <literal>-</literal> and <literal>.</literal>. The tags are stored as a
|
||||
colon-separated list in the <varname>TAGS=</varname> field of <filename>/etc/machine-info</filename>, see
|
||||
<citerefentry><refentrytitle>machine-info</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
|
||||
details. If no tags are configured this property is the empty list.</para>
|
||||
|
||||
<para>Note that <filename>systemd-hostnamed</filename> starts only on request and terminates after a
|
||||
short idle period. This effectively means that <function>PropertyChanged</function> messages are not sent
|
||||
out for changes made directly on the files (as in: administrator edits the files with vi). This is
|
||||
@@ -365,8 +380,13 @@ node /org/freedesktop/hostname1 {
|
||||
deployment environment), and <varname>Location</varname> (physical system location), respectively.
|
||||
</para>
|
||||
|
||||
<para><function>SetTags()</function> sets the machine tags exposed by the <varname>Tags</varname>
|
||||
property. It takes a list of strings, each of which must be a valid machine tag (1…255 ASCII
|
||||
alphanumeric characters, <literal>-</literal> and <literal>.</literal>). Passing an empty list removes
|
||||
the <varname>TAGS=</varname> field from <filename>/etc/machine-info</filename>.</para>
|
||||
|
||||
<para><varname>PrettyHostname</varname>, <varname>IconName</varname>, <varname>Chassis</varname>,
|
||||
<varname>Deployment</varname>, and <varname>Location</varname> are stored in
|
||||
<varname>Deployment</varname>, <varname>Location</varname>, and <varname>Tags</varname> are stored in
|
||||
<filename>/etc/machine-info</filename>. See
|
||||
<citerefentry><refentrytitle>machine-info</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
|
||||
the semantics of those settings.</para>
|
||||
@@ -400,8 +420,8 @@ node /org/freedesktop/hostname1 {
|
||||
<interfacename>org.freedesktop.hostname1.set-hostname</interfacename>. For
|
||||
<function>SetStaticHostname()</function> and <function>SetPrettyHostname()</function> it is
|
||||
<interfacename>org.freedesktop.hostname1.set-static-hostname</interfacename>. For
|
||||
<function>SetIconName()</function>, <function>SetChassis()</function>, <function>SetDeployment()</function>
|
||||
and <function>SetLocation()</function> it is
|
||||
<function>SetIconName()</function>, <function>SetChassis()</function>, <function>SetDeployment()</function>,
|
||||
<function>SetLocation()</function> and <function>SetTags()</function> it is
|
||||
<interfacename>org.freedesktop.hostname1.set-machine-info</interfacename>.</para>
|
||||
</refsect2>
|
||||
</refsect1>
|
||||
@@ -505,7 +525,8 @@ node /org/freedesktop/hostname1 {
|
||||
<varname>OperatingSystemImageVersion</varname>, <varname>HardwareSKU</varname>, and
|
||||
<varname>HardwareVersion</varname> were added in version 258.</para>
|
||||
<para><varname>OperatingSystemFancyName</varname> was added in version 260.</para>
|
||||
<para><function>GetMachineInfo()</function> was added in version 261.</para>
|
||||
<para><function>GetMachineInfo()</function>, <varname>Tags</varname> and
|
||||
<function>SetTags()</function> were added in version 261.</para>
|
||||
</refsect2>
|
||||
</refsect1>
|
||||
|
||||
|
||||
@@ -253,3 +253,72 @@ int machine_spec_valid(const char *s) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool machine_tag_is_valid(const char *s) {
|
||||
size_t n = strlen_ptr(s);
|
||||
if (n <= 0 || n >= 256)
|
||||
return false;
|
||||
|
||||
return in_charset(s, ALPHANUMERICAL "-.");
|
||||
}
|
||||
|
||||
bool machine_tag_list_is_valid(char **l) {
|
||||
size_t n = 0;
|
||||
STRV_FOREACH(i, l) {
|
||||
n++;
|
||||
if (n > MACHINE_TAGS_MAX)
|
||||
return false;
|
||||
|
||||
if (!machine_tag_is_valid(*i))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int machine_tags_from_string(const char *s, bool graceful, char ***ret) {
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
/* Parses the colon-separated TAGS= machine-info field into a sorted, deduplicated strv. Each tag is
|
||||
* validated: if 'graceful' is true invalid tags are silently dropped, otherwise an invalid tag makes
|
||||
* us fail with -EINVAL. The result is NULL if no (valid) tags remain. */
|
||||
|
||||
if (isempty(s)) {
|
||||
*ret = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
_cleanup_strv_free_ char **l = strv_split(s, ":");
|
||||
if (!l)
|
||||
return -ENOMEM;
|
||||
|
||||
strv_sort_uniq(l);
|
||||
|
||||
if (!graceful) {
|
||||
if (!machine_tag_list_is_valid(l))
|
||||
return -EINVAL;
|
||||
|
||||
*ret = strv_isempty(l) ? NULL : TAKE_PTR(l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t n = 0;
|
||||
_cleanup_strv_free_ char **cleaned = NULL;
|
||||
STRV_FOREACH(i, l) {
|
||||
if (!machine_tag_is_valid(*i))
|
||||
continue;
|
||||
|
||||
n++;
|
||||
if (n > MACHINE_TAGS_MAX)
|
||||
return -E2BIG;
|
||||
|
||||
r = strv_extend(&cleaned, *i);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(cleaned);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -47,3 +47,9 @@ int get_pretty_hostname(char **ret);
|
||||
|
||||
int machine_spec_valid(const char *s);
|
||||
int split_user_at_host(const char *s, char **ret_user, char **ret_host);
|
||||
|
||||
#define MACHINE_TAGS_MAX 1024U
|
||||
|
||||
bool machine_tag_is_valid(const char *s);
|
||||
bool machine_tag_list_is_valid(char **l);
|
||||
int machine_tags_from_string(const char *s, bool graceful, char ***ret);
|
||||
|
||||
+112
-8
@@ -43,7 +43,6 @@
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "time-util.h"
|
||||
#include "utf8.h"
|
||||
#include "varlink-io.systemd.Hostname.h"
|
||||
#include "varlink-io.systemd.service.h"
|
||||
#include "varlink-util.h"
|
||||
@@ -58,12 +57,15 @@ typedef enum {
|
||||
PROP_STATIC_HOSTNAME,
|
||||
PROP_STATIC_HOSTNAME_SUBSTITUTED_WILDCARDS,
|
||||
|
||||
/* Read from /etc/machine-info */
|
||||
/* Read from /etc/machine-info (with fallbacks) */
|
||||
PROP_PRETTY_HOSTNAME,
|
||||
_PROP_MACHINE_INFO_SETTABLE_FIRST = PROP_PRETTY_HOSTNAME,
|
||||
PROP_ICON_NAME,
|
||||
PROP_CHASSIS,
|
||||
PROP_DEPLOYMENT,
|
||||
PROP_LOCATION,
|
||||
PROP_TAGS,
|
||||
_PROP_MACHINE_INFO_SETTABLE_LAST = PROP_TAGS,
|
||||
PROP_HARDWARE_VENDOR,
|
||||
PROP_HARDWARE_MODEL,
|
||||
PROP_HARDWARE_SKU,
|
||||
@@ -77,6 +79,7 @@ typedef enum {
|
||||
PROP_OS_SUPPORT_END,
|
||||
PROP_OS_IMAGE_ID,
|
||||
PROP_OS_IMAGE_VERSION,
|
||||
|
||||
_PROP_MAX,
|
||||
_PROP_INVALID = -EINVAL,
|
||||
} HostProperty;
|
||||
@@ -173,6 +176,7 @@ static void context_read_machine_info(Context *c) {
|
||||
PROP_CHASSIS,
|
||||
PROP_DEPLOYMENT,
|
||||
PROP_LOCATION,
|
||||
PROP_TAGS,
|
||||
PROP_HARDWARE_VENDOR,
|
||||
PROP_HARDWARE_MODEL,
|
||||
PROP_HARDWARE_SKU,
|
||||
@@ -184,6 +188,7 @@ static void context_read_machine_info(Context *c) {
|
||||
"CHASSIS", &c->data[PROP_CHASSIS],
|
||||
"DEPLOYMENT", &c->data[PROP_DEPLOYMENT],
|
||||
"LOCATION", &c->data[PROP_LOCATION],
|
||||
"TAGS", &c->data[PROP_TAGS],
|
||||
"HARDWARE_VENDOR", &c->data[PROP_HARDWARE_VENDOR],
|
||||
"HARDWARE_MODEL", &c->data[PROP_HARDWARE_MODEL],
|
||||
"HARDWARE_SKU", &c->data[PROP_HARDWARE_SKU],
|
||||
@@ -840,11 +845,12 @@ static int context_write_data_static_hostname(Context *c) {
|
||||
static int context_write_data_machine_info(Context *c) {
|
||||
_cleanup_(unset_statp) struct stat *s = NULL;
|
||||
static const char * const name[_PROP_MAX] = {
|
||||
[PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
|
||||
[PROP_ICON_NAME] = "ICON_NAME",
|
||||
[PROP_CHASSIS] = "CHASSIS",
|
||||
[PROP_DEPLOYMENT] = "DEPLOYMENT",
|
||||
[PROP_LOCATION] = "LOCATION",
|
||||
[PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
|
||||
[PROP_ICON_NAME] = "ICON_NAME",
|
||||
[PROP_CHASSIS] = "CHASSIS",
|
||||
[PROP_DEPLOYMENT] = "DEPLOYMENT",
|
||||
[PROP_LOCATION] = "LOCATION",
|
||||
[PROP_TAGS] = "TAGS",
|
||||
};
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
int r;
|
||||
@@ -859,7 +865,7 @@ static int context_write_data_machine_info(Context *c) {
|
||||
if (r < 0 && r != -ENOENT)
|
||||
return r;
|
||||
|
||||
for (HostProperty p = PROP_PRETTY_HOSTNAME; p <= PROP_LOCATION; p++) {
|
||||
for (HostProperty p = _PROP_MACHINE_INFO_SETTABLE_FIRST; p <= _PROP_MACHINE_INFO_SETTABLE_LAST; p++) {
|
||||
assert(name[p]);
|
||||
|
||||
r = strv_env_assign(&l, name[p], empty_to_null(c->data[p]));
|
||||
@@ -1163,6 +1169,29 @@ static int property_get_machine_info_field(
|
||||
return sd_bus_message_append(reply, "s", *(char**) userdata);
|
||||
}
|
||||
|
||||
static int property_get_tags(
|
||||
sd_bus *bus,
|
||||
const char *path,
|
||||
const char *interface,
|
||||
const char *property,
|
||||
sd_bus_message *reply,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
Context *c = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
context_read_machine_info(c);
|
||||
|
||||
/* Silently drop any invalid tags that might have been written into the file by hand */
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
r = machine_tags_from_string(c->data[PROP_TAGS], /* graceful= */ true, &l);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to parse machine tags '%s', ignoring: %m", strnull(c->data[PROP_TAGS]));
|
||||
|
||||
return sd_bus_message_append_strv(reply, l);
|
||||
}
|
||||
|
||||
static int property_get_os_release_field(
|
||||
sd_bus *bus,
|
||||
const char *path,
|
||||
@@ -1561,6 +1590,74 @@ static int method_set_location(sd_bus_message *m, void *userdata, sd_bus_error *
|
||||
return set_machine_info(userdata, m, PROP_LOCATION, method_set_location, error);
|
||||
}
|
||||
|
||||
static int method_set_tags(sd_bus_message *m, void *userdata, sd_bus_error *error) {
|
||||
Context *c = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
_cleanup_strv_free_ char **tags = NULL;
|
||||
r = sd_bus_message_read_strv(m, &tags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
strv_sort_uniq(tags);
|
||||
|
||||
if (strv_length(tags) > MACHINE_TAGS_MAX)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Too many machine tags specified.");
|
||||
|
||||
_cleanup_free_ char *j = strv_join(tags, ":");
|
||||
if (!j)
|
||||
return log_oom();
|
||||
|
||||
if (!machine_tag_list_is_valid(tags))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid tags '%s'", j);
|
||||
|
||||
context_read_machine_info(c);
|
||||
|
||||
if (streq_ptr(empty_to_null(j), empty_to_null(c->data[PROP_TAGS])))
|
||||
return sd_bus_reply_method_return(m, NULL);
|
||||
|
||||
r = bus_verify_polkit_async_full(
|
||||
m,
|
||||
"org.freedesktop.hostname1.set-machine-info",
|
||||
/* details= */ NULL,
|
||||
/* good_user= */ UID_INVALID,
|
||||
/* flags= */ 0,
|
||||
&c->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
|
||||
|
||||
if (strv_isempty(tags))
|
||||
c->data[PROP_TAGS] = mfree(c->data[PROP_TAGS]);
|
||||
else
|
||||
free_and_replace(c->data[PROP_TAGS], j);
|
||||
|
||||
r = context_write_data_machine_info(c);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to write machine info: %m");
|
||||
if (ERRNO_IS_PRIVILEGE(r))
|
||||
return sd_bus_error_set(error, BUS_ERROR_FILE_IS_PROTECTED, "Not allowed to update /etc/machine-info.");
|
||||
if (r == -EROFS)
|
||||
return sd_bus_error_set(error, BUS_ERROR_READ_ONLY_FILESYSTEM, "/etc/machine-info is in a read-only filesystem.");
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %m");
|
||||
}
|
||||
|
||||
log_info("Changed tags to '%s'", strempty(c->data[PROP_TAGS]));
|
||||
|
||||
(void) sd_bus_emit_properties_changed(
|
||||
sd_bus_message_get_bus(m),
|
||||
"/org/freedesktop/hostname1",
|
||||
"org.freedesktop.hostname1",
|
||||
"Tags",
|
||||
NULL);
|
||||
|
||||
return sd_bus_reply_method_return(m, NULL);
|
||||
}
|
||||
|
||||
static int method_get_product_uuid(sd_bus_message *m, void *userdata, sd_bus_error *error) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
|
||||
Context *c = ASSERT_PTR(userdata);
|
||||
@@ -1645,6 +1742,7 @@ static int method_get_machine_info(sd_bus_message *m, void *userdata, sd_bus_err
|
||||
{ "CHASSIS", PROP_CHASSIS },
|
||||
{ "DEPLOYMENT", PROP_DEPLOYMENT },
|
||||
{ "LOCATION", PROP_LOCATION },
|
||||
{ "TAGS", PROP_TAGS },
|
||||
{ "HARDWARE_VENDOR", PROP_HARDWARE_VENDOR },
|
||||
{ "HARDWARE_MODEL", PROP_HARDWARE_MODEL },
|
||||
{ "HARDWARE_SKU", PROP_HARDWARE_SKU },
|
||||
@@ -1853,6 +1951,7 @@ static const sd_bus_vtable hostname_vtable[] = {
|
||||
SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
|
||||
SD_BUS_PROPERTY("Deployment", "s", property_get_machine_info_field, offsetof(Context, data[PROP_DEPLOYMENT]), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
|
||||
SD_BUS_PROPERTY("Location", "s", property_get_machine_info_field, offsetof(Context, data[PROP_LOCATION]), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
|
||||
SD_BUS_PROPERTY("Tags", "as", property_get_tags, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
|
||||
SD_BUS_PROPERTY("KernelName", "s", property_get_uname_field, offsetof(struct utsname, sysname), SD_BUS_VTABLE_ABSOLUTE_OFFSET|SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("KernelRelease", "s", property_get_uname_field, offsetof(struct utsname, release), SD_BUS_VTABLE_ABSOLUTE_OFFSET|SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("KernelVersion", "s", property_get_uname_field, offsetof(struct utsname, version), SD_BUS_VTABLE_ABSOLUTE_OFFSET|SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
@@ -1910,6 +2009,11 @@ static const sd_bus_vtable hostname_vtable[] = {
|
||||
SD_BUS_NO_RESULT,
|
||||
method_set_location,
|
||||
SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD_WITH_ARGS("SetTags",
|
||||
SD_BUS_ARGS("as", tags),
|
||||
SD_BUS_NO_RESULT,
|
||||
method_set_tags,
|
||||
SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD_WITH_ARGS("GetProductUUID",
|
||||
SD_BUS_ARGS("b", interactive),
|
||||
SD_BUS_RESULT("ay", uuid),
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "hostname-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "tests.h"
|
||||
|
||||
TEST(hostname_is_valid) {
|
||||
@@ -117,4 +119,66 @@ TEST(split_user_at_host) {
|
||||
test_split_user_at_host_one("aa@@@bb", "aa", "@@bb", 1);
|
||||
}
|
||||
|
||||
TEST(machine_tag_is_valid) {
|
||||
assert_se(machine_tag_is_valid("foo"));
|
||||
assert_se(machine_tag_is_valid("foo-bar.baz"));
|
||||
assert_se(machine_tag_is_valid("Webserver01"));
|
||||
assert_se(machine_tag_is_valid("a"));
|
||||
|
||||
assert_se(!machine_tag_is_valid(NULL));
|
||||
assert_se(!machine_tag_is_valid(""));
|
||||
assert_se(!machine_tag_is_valid("foo:bar")); /* colon is the separator */
|
||||
assert_se(!machine_tag_is_valid("foo bar"));
|
||||
assert_se(!machine_tag_is_valid("fööbar")); /* non-ASCII */
|
||||
assert_se(!machine_tag_is_valid("foo/bar"));
|
||||
assert_se(!machine_tag_is_valid("foo_bar"));
|
||||
|
||||
/* Length boundary: 255 characters is fine, 256 is too long */
|
||||
_cleanup_free_ char *max = strrep("a", 255), *over = strrep("a", 256);
|
||||
assert_se(max);
|
||||
assert_se(over);
|
||||
assert_se(machine_tag_is_valid(max));
|
||||
assert_se(!machine_tag_is_valid(over));
|
||||
}
|
||||
|
||||
TEST(machine_tag_list_is_valid) {
|
||||
assert_se(machine_tag_list_is_valid(NULL)); /* empty list is valid */
|
||||
assert_se(machine_tag_list_is_valid(STRV_MAKE("a")));
|
||||
assert_se(machine_tag_list_is_valid(STRV_MAKE("foo", "bar", "c-d.e")));
|
||||
|
||||
assert_se(!machine_tag_list_is_valid(STRV_MAKE("foo", "b:c")));
|
||||
assert_se(!machine_tag_list_is_valid(STRV_MAKE("foo", "")));
|
||||
}
|
||||
|
||||
TEST(machine_tags_from_string) {
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
|
||||
ASSERT_OK(machine_tags_from_string(NULL, /* graceful= */ false, &l));
|
||||
assert_se(strv_isempty(l));
|
||||
l = strv_free(l);
|
||||
|
||||
ASSERT_OK(machine_tags_from_string("", /* graceful= */ true, &l));
|
||||
assert_se(strv_isempty(l));
|
||||
l = strv_free(l);
|
||||
|
||||
/* Sorted and deduplicated */
|
||||
ASSERT_OK(machine_tags_from_string("foo:bar:foo:baz", /* graceful= */ false, &l));
|
||||
assert_se(strv_equal(l, STRV_MAKE("bar", "baz", "foo")));
|
||||
l = strv_free(l);
|
||||
|
||||
/* Graceful: invalid tags are dropped, valid ones kept (sorted/deduplicated) */
|
||||
ASSERT_OK(machine_tags_from_string("foo:in valid:bar:foo", /* graceful= */ true, &l));
|
||||
assert_se(strv_equal(l, STRV_MAKE("bar", "foo")));
|
||||
l = strv_free(l);
|
||||
|
||||
/* Graceful: all tags invalid → empty list */
|
||||
ASSERT_OK(machine_tags_from_string("in valid:also invalid", /* graceful= */ true, &l));
|
||||
assert_se(strv_isempty(l));
|
||||
l = strv_free(l);
|
||||
|
||||
/* Fatal: a single invalid tag fails the whole parse */
|
||||
ASSERT_ERROR(machine_tags_from_string("foo:in valid:bar", /* graceful= */ false, &l), EINVAL);
|
||||
assert_se(!l);
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_DEBUG);
|
||||
|
||||
Reference in New Issue
Block a user