hostnamed,pid1,firstboot: introduce "machine tags" concept (#42223)
Fixes: #38591
This commit is contained in:
@@ -159,8 +159,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
- sysupdate: add concept for enabling/disabling specific components explicitly,
|
||||
just like features.
|
||||
|
||||
- machine-info: add a TAGS concept that can be used to categorize a machine
|
||||
|
||||
- udev: add a MACHINE_TAGS field, that augments /etc/machine-info configured
|
||||
tags.
|
||||
|
||||
@@ -180,13 +178,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
- pcrextend: we should measure something when we enter developer mode, by some
|
||||
definition of developer mode.
|
||||
|
||||
- /etc/machine-info should have a concept of a "role" that we can put a machine
|
||||
into, which can be consumed by sysupdate and similar. A role should be
|
||||
something we can set once (i.e. the initial setting should be protected by
|
||||
polkit and be somewhat loosely access control, and later settings should use a
|
||||
different/tougher polkit authorization, so that people can implement a
|
||||
no-way-back mechanism)
|
||||
|
||||
- firstboot: optionally accept credentials at firstboot without authentication
|
||||
|
||||
- firstboot/sysinstall: add simple interface for prompting users to enable
|
||||
@@ -788,11 +779,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
- sysext: make systemd-{sys,conf}ext-sysroot.service work in the split `/var`
|
||||
configuration.
|
||||
|
||||
- introduce a concept of /etc/machine-info "TAGS=" field that allows tagging
|
||||
machines with zero, one or more roles, states or other forms of
|
||||
categorization. Then, add a way of using this in sysupdate to automatically
|
||||
enable certain transfers, one for each role.
|
||||
|
||||
- sd-varlink: add fully async modes of the protocol upgrade stuff
|
||||
|
||||
- repart: maybe remove iso9660/eltorito superblock from disk when booting via
|
||||
|
||||
@@ -165,6 +165,32 @@
|
||||
<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>
|
||||
|
||||
|
||||
+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>
|
||||
|
||||
|
||||
@@ -67,6 +67,8 @@
|
||||
|
||||
<listitem><para>The system hostname</para></listitem>
|
||||
|
||||
<listitem><para>The machine tags</para></listitem>
|
||||
|
||||
<listitem><para>The kernel command line used when installing kernel images</para></listitem>
|
||||
|
||||
<listitem><para>The root user's password and shell</para></listitem>
|
||||
@@ -198,6 +200,27 @@
|
||||
<xi:include href="version-info.xml" xpointer="v216"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--machine-tags=<replaceable>TAG</replaceable><optional>:<replaceable>TAG</replaceable>…</optional></option></term>
|
||||
|
||||
<listitem><para>Set the machine tags to the specified colon-separated list. 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>. This
|
||||
controls the <varname>TAGS=</varname> field of
|
||||
<citerefentry><refentrytitle>machine-info</refentrytitle><manvolnum>5</manvolnum></citerefentry>. The
|
||||
tags may later be queried and changed with the <command>tags</command> command of
|
||||
<citerefentry><refentrytitle>hostnamectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, and
|
||||
matched against with the <varname>ConditionMachineTag=</varname>/<varname>AssertMachineTag=</varname>
|
||||
unit settings, see
|
||||
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
|
||||
|
||||
<para>Unlike most other settings, machine tags are not prompted for interactively.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--root-password=<replaceable>PASSWORD</replaceable></option></term>
|
||||
<term><option>--root-password-file=<replaceable>PATH</replaceable></option></term>
|
||||
@@ -458,6 +481,18 @@
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>firstboot.machine-tags</varname></term>
|
||||
|
||||
<listitem><para>This credential specifies the machine tags to set during first boot, as a
|
||||
colon-separated list, equivalent to the <option>--machine-tags=</option> switch described above. The
|
||||
tags are written into the <varname>TAGS=</varname> field of <filename>/etc/machine-info</filename>
|
||||
(if that file is not already present), and only have an effect on first boot. If the list contains
|
||||
invalid tags it is ignored in its entirety.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
|
||||
<para>Note that by default the <filename>systemd-firstboot.service</filename> unit file is set up to
|
||||
|
||||
@@ -91,6 +91,21 @@
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>firstboot.machine-tags</varname></term>
|
||||
<listitem>
|
||||
<para>The machine tags to set, as a colon-separated list (e.g.
|
||||
<literal>webserver:frontend:berlin</literal>). Read by
|
||||
<citerefentry><refentrytitle>systemd-firstboot</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
and only honoured if <filename>/etc/machine-info</filename> has not been configured before. Written
|
||||
into the <varname>TAGS=</varname> field of that file, see
|
||||
<citerefentry><refentrytitle>machine-info</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
|
||||
details.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>login.issue</varname></term>
|
||||
<listitem>
|
||||
|
||||
@@ -2058,6 +2058,25 @@
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>ConditionMachineTag=</varname></term>
|
||||
|
||||
<listitem><para><varname>ConditionMachineTag=</varname> may be used to match against the tags
|
||||
assigned to the local machine. Machine tags are short labels that classify and group machines for
|
||||
management purposes; they are configured in the <varname>TAGS=</varname> field of
|
||||
<citerefentry><refentrytitle>machine-info</refentrytitle><manvolnum>5</manvolnum></citerefentry> and
|
||||
may be queried and changed with the <command>tags</command> command of
|
||||
<citerefentry><refentrytitle>hostnamectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>. The
|
||||
argument is a single tag pattern, which is compared against each of the configured tags using
|
||||
shell-style globbing (<literal>*</literal>, <literal>?</literal>, <literal>[]</literal>). The
|
||||
condition is satisfied if at least one of the configured tags matches the pattern. The test may be
|
||||
negated by prepending an exclamation mark, in which case it is satisfied if none of the configured
|
||||
tags matches.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>ConditionMemoryPressure=</varname></term>
|
||||
<term><varname>ConditionCPUPressure=</varname></term>
|
||||
@@ -2126,6 +2145,7 @@
|
||||
<term><varname>AssertCPUs=</varname></term>
|
||||
<term><varname>AssertCPUFeature=</varname></term>
|
||||
<term><varname>AssertOSRelease=</varname></term>
|
||||
<term><varname>AssertMachineTag=</varname></term>
|
||||
<term><varname>AssertMemoryPressure=</varname></term>
|
||||
<term><varname>AssertCPUPressure=</varname></term>
|
||||
<term><varname>AssertIOPressure=</varname></term>
|
||||
|
||||
@@ -67,7 +67,7 @@ _hostnamectl() {
|
||||
local -A VERBS=(
|
||||
[STANDALONE]='status'
|
||||
[ICONS]='icon-name'
|
||||
[NAME]='hostname deployment location'
|
||||
[NAME]='hostname deployment location tags'
|
||||
[CHASSIS]='chassis'
|
||||
)
|
||||
|
||||
|
||||
@@ -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 "$@"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -398,6 +398,7 @@ Unit.ConditionUser, config_parse_unit_condition_string
|
||||
Unit.ConditionGroup, config_parse_unit_condition_string, CONDITION_GROUP, offsetof(Unit, conditions)
|
||||
Unit.ConditionControlGroupController, config_parse_unit_condition_string, CONDITION_CONTROL_GROUP_CONTROLLER, offsetof(Unit, conditions)
|
||||
Unit.ConditionOSRelease, config_parse_unit_condition_string, CONDITION_OS_RELEASE, offsetof(Unit, conditions)
|
||||
Unit.ConditionMachineTag, config_parse_unit_condition_string, CONDITION_MACHINE_TAG, offsetof(Unit, conditions)
|
||||
Unit.ConditionMemoryPressure, config_parse_unit_condition_string, CONDITION_MEMORY_PRESSURE, offsetof(Unit, conditions)
|
||||
Unit.ConditionCPUPressure, config_parse_unit_condition_string, CONDITION_CPU_PRESSURE, offsetof(Unit, conditions)
|
||||
Unit.ConditionIOPressure, config_parse_unit_condition_string, CONDITION_IO_PRESSURE, offsetof(Unit, conditions)
|
||||
@@ -434,6 +435,7 @@ Unit.AssertUser, config_parse_unit_condition_string
|
||||
Unit.AssertGroup, config_parse_unit_condition_string, CONDITION_GROUP, offsetof(Unit, asserts)
|
||||
Unit.AssertControlGroupController, config_parse_unit_condition_string, CONDITION_CONTROL_GROUP_CONTROLLER, offsetof(Unit, asserts)
|
||||
Unit.AssertOSRelease, config_parse_unit_condition_string, CONDITION_OS_RELEASE, offsetof(Unit, asserts)
|
||||
Unit.AssertMachineTag, config_parse_unit_condition_string, CONDITION_MACHINE_TAG, offsetof(Unit, asserts)
|
||||
Unit.AssertMemoryPressure, config_parse_unit_condition_string, CONDITION_MEMORY_PRESSURE, offsetof(Unit, asserts)
|
||||
Unit.AssertCPUPressure, config_parse_unit_condition_string, CONDITION_CPU_PRESSURE, offsetof(Unit, asserts)
|
||||
Unit.AssertIOPressure, config_parse_unit_condition_string, CONDITION_IO_PRESSURE, offsetof(Unit, asserts)
|
||||
|
||||
@@ -67,6 +67,7 @@ static char *arg_keymap = NULL;
|
||||
static char *arg_timezone = NULL;
|
||||
static char *arg_hostname = NULL;
|
||||
static sd_id128_t arg_machine_id = {};
|
||||
static char **arg_machine_tags = NULL;
|
||||
static char *arg_root_password = NULL;
|
||||
static char *arg_root_shell = NULL;
|
||||
static char *arg_kernel_cmdline = NULL;
|
||||
@@ -98,6 +99,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_locale_messages, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_keymap, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_timezone, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_hostname, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_machine_tags, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_root_password, erase_and_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_root_shell, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline, freep);
|
||||
@@ -778,6 +780,71 @@ static int process_machine_id(int rfd) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int process_machine_tags(int rfd) {
|
||||
int r;
|
||||
|
||||
assert(rfd >= 0);
|
||||
|
||||
_cleanup_free_ char *f = NULL;
|
||||
_cleanup_close_ int pfd = chase_and_open_parent_at(
|
||||
/* root_fd= */ rfd,
|
||||
/* dir_fd= */ rfd,
|
||||
"/etc/machine-info",
|
||||
CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW,
|
||||
&f);
|
||||
if (pfd < 0)
|
||||
return log_error_errno(pfd, "Failed to chase /etc/machine-info parent: %m");
|
||||
|
||||
r = should_configure(pfd, f);
|
||||
if (r == 0)
|
||||
log_debug("Found /etc/machine-info, assuming machine tags have been configured.");
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
if (!arg_machine_tags) {
|
||||
_cleanup_free_ char *tags = NULL;
|
||||
r = read_credential("firstboot.machine-tags", (void**) &tags, /* ret_size= */ NULL);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to read credential firstboot.machine-tags, ignoring: %m");
|
||||
else {
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
r = machine_tags_from_string(tags, /* graceful= */ false, &l);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to parse machine tags '%s', ignoring credential: %m", tags);
|
||||
else {
|
||||
strv_free_and_replace(arg_machine_tags, l);
|
||||
log_debug("Acquired machine tags list from credentials.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* NB: We do not prompt for machine tags, at least not for now */
|
||||
|
||||
if (!arg_machine_tags) {
|
||||
log_debug("Initialization of machine tags was not requested, skipping.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *j = strv_join(arg_machine_tags, ":");
|
||||
if (!j)
|
||||
return log_oom();
|
||||
|
||||
_cleanup_free_ char *c = strjoin("TAGS=\"", j, "\"\n");
|
||||
if (!c)
|
||||
return log_oom();
|
||||
|
||||
r = write_string_file_at(
|
||||
pfd,
|
||||
"machine-info",
|
||||
c,
|
||||
WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_LABEL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write /etc/machine-info: %m");
|
||||
|
||||
log_info("/etc/machine-info written.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int prompt_root_password(int rfd, sd_varlink **mute_console_link) {
|
||||
const char *msg1, *msg2;
|
||||
int r;
|
||||
@@ -1363,6 +1430,16 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
return log_error_errno(r, "Failed to parse machine id %s.", opts.arg);
|
||||
break;
|
||||
|
||||
OPTION_LONG("machine-tags", "TAG[:…]", "Set machine tags"): {
|
||||
_cleanup_strv_free_ char **tags = NULL;
|
||||
r = machine_tags_from_string(opts.arg, /* graceful= */ false, &tags);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse machine tags '%s': %m", opts.arg);
|
||||
|
||||
strv_free_and_replace(arg_machine_tags, tags);
|
||||
break;
|
||||
}
|
||||
|
||||
OPTION_LONG("root-password", "PASSWORD", "Set root password from plaintext password"):
|
||||
r = free_and_strdup_warn(&arg_root_password, opts.arg);
|
||||
if (r < 0)
|
||||
@@ -1693,6 +1770,10 @@ static int run(int argc, char *argv[]) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = process_machine_tags(rfd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+88
-15
@@ -9,6 +9,7 @@
|
||||
#include "sd-json.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "ansi-color.h"
|
||||
#include "build.h"
|
||||
#include "bus-common-errors.h"
|
||||
#include "bus-error.h"
|
||||
@@ -18,6 +19,7 @@
|
||||
#include "bus-util.h"
|
||||
#include "errno-util.h"
|
||||
#include "format-table.h"
|
||||
#include "help-util.h"
|
||||
#include "hostname-setup.h"
|
||||
#include "hostname-util.h"
|
||||
#include "log.h"
|
||||
@@ -26,9 +28,9 @@
|
||||
#include "os-util.h"
|
||||
#include "parse-argument.h"
|
||||
#include "polkit-agent.h"
|
||||
#include "pretty-print.h"
|
||||
#include "runtime-scope.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "time-util.h"
|
||||
#include "verbs.h"
|
||||
|
||||
@@ -49,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;
|
||||
@@ -196,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",
|
||||
@@ -411,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,
|
||||
@@ -427,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) },
|
||||
@@ -719,14 +741,67 @@ static int verb_get_or_set_location(int argc, char *argv[], uintptr_t _data, voi
|
||||
set_simple_string(userdata, "location", "SetLocation", argv[1]);
|
||||
}
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
_cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL;
|
||||
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;
|
||||
|
||||
r = terminal_urlify_man("hostnamectl", "1", &link);
|
||||
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 log_oom();
|
||||
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;
|
||||
|
||||
r = option_parser_get_help_table(&options);
|
||||
if (r < 0)
|
||||
@@ -738,22 +813,20 @@ static int help(void) {
|
||||
|
||||
(void) table_sync_column_widths(0, options, verbs);
|
||||
|
||||
printf("%s [OPTIONS...] COMMAND ...\n"
|
||||
"\n%sQuery or change system hostname.%s\n"
|
||||
"\nCommands:\n",
|
||||
program_invocation_short_name,
|
||||
ansi_highlight(),
|
||||
ansi_normal());
|
||||
help_cmdline("[OPTIONS...] COMMAND ...");
|
||||
help_abstract("Query or change system hostname.");
|
||||
|
||||
help_section("Commands");
|
||||
r = table_print_or_warn(verbs);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("\nOptions:\n");
|
||||
help_section("Options");
|
||||
r = table_print_or_warn(options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("\nSee the %s for details.\n", link);
|
||||
help_man_page_reference("hostnamectl", "1");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+114
-22
@@ -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],
|
||||
@@ -321,18 +326,6 @@ static int context_acquire_device_tree(Context *c) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool string_is_safe_for_dbus(const char *s) {
|
||||
assert(s);
|
||||
|
||||
/* Do some superficial validation: do not allow CCs and make sure D-Bus won't kick us off the bus
|
||||
* because we send invalid UTF-8 data */
|
||||
|
||||
if (string_has_cc(s, /* ok= */ NULL))
|
||||
return false;
|
||||
|
||||
return utf8_is_valid(s);
|
||||
}
|
||||
|
||||
static int get_dmi_property(Context *c, const char *key, char **ret) {
|
||||
const char *s;
|
||||
int r;
|
||||
@@ -348,7 +341,7 @@ static int get_dmi_property(Context *c, const char *key, char **ret) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!string_is_safe_for_dbus(s))
|
||||
if (!string_is_safe(s, STRING_ALLOW_EMPTY|STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES|STRING_ALLOW_GLOBS))
|
||||
return -ENXIO;
|
||||
|
||||
return strdup_to(ret, s);
|
||||
@@ -447,7 +440,7 @@ static int get_sysattr(sd_device *device, const char *key, char **ret) {
|
||||
if (r < 0)
|
||||
return log_device_debug_errno(device, r, "Failed to read '%s' attribute: %m", key);
|
||||
|
||||
if (!string_is_safe_for_dbus(s))
|
||||
if (!string_is_safe(s, STRING_ALLOW_EMPTY|STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES|STRING_ALLOW_GLOBS))
|
||||
return log_device_debug_errno(device, SYNTHETIC_ERRNO(ENXIO),
|
||||
"'%s' attribute is not safe for exposing through DBus: %s",
|
||||
key, s);
|
||||
@@ -852,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;
|
||||
@@ -871,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]));
|
||||
@@ -1175,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,
|
||||
@@ -1573,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);
|
||||
@@ -1657,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 },
|
||||
@@ -1865,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),
|
||||
@@ -1922,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),
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "glob-util.h"
|
||||
#include "hmac.h"
|
||||
#include "hostname-setup.h"
|
||||
#include "hostname-util.h"
|
||||
#include "id128-util.h"
|
||||
#include "ima-util.h"
|
||||
#include "initrd-util.h"
|
||||
@@ -316,6 +317,37 @@ static int condition_test_osrelease(Condition *c, char **env) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static int condition_test_machine_tag(Condition *c, char **env) {
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
assert(c->parameter);
|
||||
assert(c->type == CONDITION_MACHINE_TAG);
|
||||
|
||||
_cleanup_free_ char *tags = NULL;
|
||||
r = parse_env_file(
|
||||
/* f= */ NULL, etc_machine_info(),
|
||||
"TAGS", &tags);
|
||||
if (r < 0 && r != -ENOENT) {
|
||||
log_debug_errno(r, "Failed to read /etc/machine-info, ignoring: %m");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Silently ignore invalid tags, matching the Tags D-Bus property */
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
r = machine_tags_from_string(tags, /* graceful= */ true, &l);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to parse machine tags '%s', ignoring: %m", tags);
|
||||
return false;
|
||||
}
|
||||
|
||||
STRV_FOREACH(i, l)
|
||||
if (fnmatch(c->parameter, *i, /* flags= */ 0) == 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int condition_test_memory(Condition *c, char **env) {
|
||||
CompareOperator operator;
|
||||
uint64_t m, k;
|
||||
@@ -1344,6 +1376,7 @@ int condition_test(Condition *c, char **env) {
|
||||
[CONDITION_ENVIRONMENT] = condition_test_environment,
|
||||
[CONDITION_CPU_FEATURE] = condition_test_cpufeature,
|
||||
[CONDITION_OS_RELEASE] = condition_test_osrelease,
|
||||
[CONDITION_MACHINE_TAG] = condition_test_machine_tag,
|
||||
[CONDITION_MEMORY_PRESSURE] = condition_test_psi,
|
||||
[CONDITION_CPU_PRESSURE] = condition_test_psi,
|
||||
[CONDITION_IO_PRESSURE] = condition_test_psi,
|
||||
@@ -1471,6 +1504,7 @@ static const char* const _condition_type_table[_CONDITION_TYPE_MAX] = {
|
||||
[CONDITION_ENVIRONMENT] = "ConditionEnvironment",
|
||||
[CONDITION_CPU_FEATURE] = "ConditionCPUFeature",
|
||||
[CONDITION_OS_RELEASE] = "ConditionOSRelease",
|
||||
[CONDITION_MACHINE_TAG] = "ConditionMachineTag",
|
||||
[CONDITION_MEMORY_PRESSURE] = "ConditionMemoryPressure",
|
||||
[CONDITION_CPU_PRESSURE] = "ConditionCPUPressure",
|
||||
[CONDITION_IO_PRESSURE] = "ConditionIOPressure",
|
||||
@@ -1528,6 +1562,7 @@ static const char* const _assert_type_table[_CONDITION_TYPE_MAX] = {
|
||||
[CONDITION_ENVIRONMENT] = "AssertEnvironment",
|
||||
[CONDITION_CPU_FEATURE] = "AssertCPUFeature",
|
||||
[CONDITION_OS_RELEASE] = "AssertOSRelease",
|
||||
[CONDITION_MACHINE_TAG] = "AssertMachineTag",
|
||||
[CONDITION_MEMORY_PRESSURE] = "AssertMemoryPressure",
|
||||
[CONDITION_CPU_PRESSURE] = "AssertCPUPressure",
|
||||
[CONDITION_IO_PRESSURE] = "AssertIOPressure",
|
||||
|
||||
@@ -21,6 +21,7 @@ typedef enum ConditionType {
|
||||
CONDITION_ENVIRONMENT,
|
||||
CONDITION_CPU_FEATURE,
|
||||
CONDITION_OS_RELEASE,
|
||||
CONDITION_MACHINE_TAG,
|
||||
CONDITION_MEMORY_PRESSURE,
|
||||
CONDITION_CPU_PRESSURE,
|
||||
CONDITION_IO_PRESSURE,
|
||||
|
||||
@@ -1414,6 +1414,69 @@ TEST(condition_test_os_release) {
|
||||
condition_free(condition);
|
||||
}
|
||||
|
||||
TEST(condition_test_machine_tag) {
|
||||
Condition *condition;
|
||||
|
||||
/* etc_machine_info() caches the path on first use, so redirect it before anything reads it and
|
||||
* rewrite the same file (truncating) for each scenario rather than switching paths. */
|
||||
_cleanup_free_ char *saved = NULL;
|
||||
ASSERT_OK(free_and_strdup(&saved, getenv("SYSTEMD_ETC_MACHINE_INFO")));
|
||||
|
||||
_cleanup_(rm_rf_physical_and_freep) char *d = NULL;
|
||||
ASSERT_OK(mkdtemp_malloc(NULL, &d));
|
||||
|
||||
_cleanup_free_ char *f = path_join(d, "machine-info");
|
||||
ASSERT_NOT_NULL(f);
|
||||
ASSERT_OK_ERRNO(setenv("SYSTEMD_ETC_MACHINE_INFO", f, /* overwrite= */ true));
|
||||
|
||||
ASSERT_OK(write_string_file(f, "TAGS=\"webserver:frontend:berlin\"\n",
|
||||
WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE));
|
||||
|
||||
/* Exact match */
|
||||
ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "webserver", /* trigger= */ false, /* negate= */ false)));
|
||||
ASSERT_OK_POSITIVE(condition_test(condition, environ));
|
||||
condition_free(condition);
|
||||
|
||||
/* Glob match */
|
||||
ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "front*", /* trigger= */ false, /* negate= */ false)));
|
||||
ASSERT_OK_POSITIVE(condition_test(condition, environ));
|
||||
condition_free(condition);
|
||||
|
||||
/* No match */
|
||||
ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "database", /* trigger= */ false, /* negate= */ false)));
|
||||
ASSERT_OK_ZERO(condition_test(condition, environ));
|
||||
condition_free(condition);
|
||||
|
||||
/* Negation matches when the tag is absent, ... */
|
||||
ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "database", /* trigger= */ false, /* negate= */ true)));
|
||||
ASSERT_OK_POSITIVE(condition_test(condition, environ));
|
||||
condition_free(condition);
|
||||
|
||||
/* ... and does not match when the tag is present */
|
||||
ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "webserver", /* trigger= */ false, /* negate= */ true)));
|
||||
ASSERT_OK_ZERO(condition_test(condition, environ));
|
||||
condition_free(condition);
|
||||
|
||||
/* Invalid tags in the file are ignored (matching the Tags D-Bus property) */
|
||||
ASSERT_OK(write_string_file(f, "TAGS=\"in valid:good\"\n",
|
||||
WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE));
|
||||
ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "in valid", /* trigger= */ false, /* negate= */ false)));
|
||||
ASSERT_OK_ZERO(condition_test(condition, environ));
|
||||
condition_free(condition);
|
||||
ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "good", /* trigger= */ false, /* negate= */ false)));
|
||||
ASSERT_OK_POSITIVE(condition_test(condition, environ));
|
||||
condition_free(condition);
|
||||
|
||||
/* No tags configured at all → never matches */
|
||||
ASSERT_OK(write_string_file(f, "PRETTY_HOSTNAME=\"x\"\n",
|
||||
WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE));
|
||||
ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "webserver", /* trigger= */ false, /* negate= */ false)));
|
||||
ASSERT_OK_ZERO(condition_test(condition, environ));
|
||||
condition_free(condition);
|
||||
|
||||
ASSERT_OK(set_unset_env("SYSTEMD_ETC_MACHINE_INFO", saved, /* overwrite= */ true));
|
||||
}
|
||||
|
||||
TEST(condition_test_psi) {
|
||||
Condition *condition;
|
||||
CGroupMask mask;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -24,6 +24,7 @@ AssertHost=
|
||||
AssertIOPressure=
|
||||
AssertKernelCommandLine=
|
||||
AssertKernelVersion=
|
||||
AssertMachineTag=
|
||||
AssertMemoryPressure=
|
||||
AssertNeedsUpdate=
|
||||
AssertOSRelease=
|
||||
@@ -72,6 +73,7 @@ ConditionHost=
|
||||
ConditionIOPressure=
|
||||
ConditionKernelCommandLine=
|
||||
ConditionKernelVersion=
|
||||
ConditionMachineTag=
|
||||
ConditionMemoryPressure=
|
||||
ConditionNeedsUpdate=
|
||||
ConditionOSRelease=
|
||||
|
||||
Executable
+139
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# shellcheck disable=SC1091
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
# shellcheck source=test/units/util.sh
|
||||
. "$(dirname "$0")"/util.sh
|
||||
|
||||
if ! command -v hostnamectl >/dev/null; then
|
||||
echo "hostnamectl not found, skipping the test"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
at_exit() {
|
||||
set +e
|
||||
|
||||
# Restore the original /etc/machine-info (if any) and make hostnamed read it again
|
||||
if [[ -e /tmp/machine-info.bak ]]; then
|
||||
mv /tmp/machine-info.bak /etc/machine-info
|
||||
else
|
||||
rm -f /etc/machine-info
|
||||
fi
|
||||
systemctl stop --job-mode=replace-irreversibly systemd-hostnamed.service
|
||||
systemctl reset-failed systemd-hostnamed.service
|
||||
|
||||
rm -fr "${ROOT:-}"
|
||||
}
|
||||
|
||||
trap at_exit EXIT
|
||||
|
||||
# Read the TAGS= field out of /etc/machine-info (robust against the file being
|
||||
# absent and against TAGS= being unset).
|
||||
get_tags_from_file() (
|
||||
set +u
|
||||
[[ -f /etc/machine-info ]] && . /etc/machine-info
|
||||
echo "${TAGS:-}"
|
||||
)
|
||||
|
||||
if [[ -f /etc/machine-info ]]; then
|
||||
cp /etc/machine-info /tmp/machine-info.bak
|
||||
fi
|
||||
|
||||
# Start from a clean slate, and make sure hostnamed re-reads the (now missing) file.
|
||||
rm -f /etc/machine-info
|
||||
systemctl stop --job-mode=replace-irreversibly systemd-hostnamed.service || :
|
||||
systemctl reset-failed systemd-hostnamed.service || :
|
||||
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
# hostnamectl tags <-> /etc/machine-info
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
|
||||
# No tags are configured initially.
|
||||
assert_eq "$(hostnamectl tags)" ""
|
||||
|
||||
# Set some tags. Multiple arguments, colon-separated arguments, duplicates and arbitrary order are
|
||||
# all accepted and normalized into a sorted, deduplicated, colon-separated list.
|
||||
hostnamectl tags webserver:frontend frontend berlin
|
||||
assert_eq "$(hostnamectl tags)" "berlin:frontend:webserver"
|
||||
|
||||
# The very same list must show up in the TAGS= field of /etc/machine-info.
|
||||
grep -qE '^TAGS="?berlin:frontend:webserver"?$' /etc/machine-info
|
||||
assert_eq "$(get_tags_from_file)" "berlin:frontend:webserver"
|
||||
|
||||
# Setting tags again replaces the previous list rather than extending it.
|
||||
hostnamectl tags database
|
||||
assert_eq "$(hostnamectl tags)" "database"
|
||||
assert_eq "$(get_tags_from_file)" "database"
|
||||
|
||||
# Invalid tags (only ASCII alphanumerics, '-' and '.' are allowed) are refused, leaving the
|
||||
# previously configured tags untouched.
|
||||
(! hostnamectl tags "invalid tag")
|
||||
(! hostnamectl tags "invalid/tag")
|
||||
assert_eq "$(hostnamectl tags)" "database"
|
||||
|
||||
# Clearing all tags via a single empty string argument. When TAGS= was the only field, hostnamed
|
||||
# removes /etc/machine-info altogether.
|
||||
hostnamectl tags ""
|
||||
assert_eq "$(hostnamectl tags)" ""
|
||||
assert_eq "$(get_tags_from_file)" ""
|
||||
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
# ConditionMachineTag=/AssertMachineTag= in a transient unit
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
|
||||
hostnamectl tags alpha:beta:gamma
|
||||
assert_eq "$(hostnamectl tags)" "alpha:beta:gamma"
|
||||
|
||||
# When the condition matches, the unit is started and 'false' actually runs, so systemd-run fails.
|
||||
# When the condition does not match, the unit is skipped (not failed) and systemd-run succeeds.
|
||||
(! systemd-run --wait --pipe -p ConditionMachineTag=beta false)
|
||||
systemd-run --wait --pipe -p ConditionMachineTag=delta false
|
||||
# Globs are matched against each individual tag.
|
||||
(! systemd-run --wait --pipe -p ConditionMachineTag='al*' false)
|
||||
systemd-run --wait --pipe -p ConditionMachineTag='z*' false
|
||||
# Negation inverts the result.
|
||||
systemd-run --wait --pipe -p ConditionMachineTag='!beta' false
|
||||
(! systemd-run --wait --pipe -p ConditionMachineTag='!delta' false)
|
||||
|
||||
# Asserts behave like conditions, except a failing assert puts the unit into a failed state, which
|
||||
# systemd-run propagates as a non-zero exit code.
|
||||
systemd-run -p AssertMachineTag=beta -p Type=oneshot true
|
||||
(! systemd-run -p AssertMachineTag=delta -p Type=oneshot true)
|
||||
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
# systemd-firstboot --machine-tags= and the firstboot.machine-tags credential
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
|
||||
if command -v systemd-firstboot >/dev/null; then
|
||||
# The firstboot.machine-tags credential is split on ':', deduplicated and sorted, and written
|
||||
# into /etc/machine-info underneath the target root.
|
||||
ROOT="$(mktemp -d)"
|
||||
systemd-run --wait --pipe --service-type=exec \
|
||||
-p SetCredential=firstboot.machine-tags:webserver:frontend:webserver:berlin \
|
||||
systemd-firstboot --root="$ROOT"
|
||||
grep -qE '^TAGS="?berlin:frontend:webserver"?$' "$ROOT/etc/machine-info"
|
||||
|
||||
# An invalid tag anywhere in the credential causes the whole list to be ignored, so no
|
||||
# machine-info file is written.
|
||||
rm -fr "$ROOT"
|
||||
ROOT="$(mktemp -d)"
|
||||
systemd-run --wait --pipe --service-type=exec \
|
||||
-p SetCredential=firstboot.machine-tags:'good:bad/tag' \
|
||||
systemd-firstboot --root="$ROOT"
|
||||
test ! -e "$ROOT/etc/machine-info"
|
||||
|
||||
# The --machine-tags= switch is normalized the same way and takes precedence over the credential.
|
||||
rm -fr "$ROOT"
|
||||
ROOT="$(mktemp -d)"
|
||||
systemd-run --wait --pipe --service-type=exec \
|
||||
-p SetCredential=firstboot.machine-tags:ignored \
|
||||
systemd-firstboot --root="$ROOT" --machine-tags=database:cache:database
|
||||
grep -qE '^TAGS="?cache:database"?$' "$ROOT/etc/machine-info"
|
||||
|
||||
# An invalid tag passed on the command line is a hard error.
|
||||
rm -fr "$ROOT"
|
||||
ROOT="$(mktemp -d)"
|
||||
(! systemd-firstboot --root="$ROOT" --machine-tags='good:bad/tag')
|
||||
fi
|
||||
Reference in New Issue
Block a user