Other systems (kubernetes…) allow tagging machines with key/value pairs.
Let's extend our allowed syntax slightly to allow that too. Thankfully,
we enforced a pretty strict ruleset on machine tags, hence we can
introduce this without breaking compatibility.
This basically allows tags to contain "=". If so, then the left-hand
side of it must be unique among machine tags.
When matching against a machine tag, we apply the same rules as before.
This means, that if people want to check if a tag with value applies
they can do:
ConditionMachineTag=foo=bar
If they just want to check if "foo=" is set to anything, they can use
the usual glob matching:
ConditionMachineTag=foo=*
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
+ <literal>-</literal>, <literal>.</literal> and <literal>=</literal>. A tag may optionally be
+ parameterized with a value, in the form
+ <literal><replaceable>key</replaceable>=<replaceable>value</replaceable></literal>, in which case the
+ same key may not be assigned more than one distinct value. 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
<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>
+ alphanumeric characters, <literal>-</literal>, <literal>.</literal> and <literal>=</literal>. The
+ first character may not be <literal>-</literal>, <literal>.</literal> or <literal>=</literal>, and
+ the last character may not be <literal>-</literal> or <literal>.</literal> (unless it takes the
+ parameterized form, see below).</para>
+
+ <para>A tag may optionally be parameterized with a value, in the form
+ <literal><replaceable>key</replaceable>=<replaceable>value</replaceable></literal>. The first
+ <literal>=</literal> separates the key from the value; any further <literal>=</literal> characters
+ are part of the value. The key (the part before the first <literal>=</literal>) follows the same
+ restrictions as an unparameterized tag, in particular it may not be empty and may not end in
+ <literal>-</literal> or <literal>.</literal>. The value (the part after the first
+ <literal>=</literal>) may be empty and is otherwise unrestricted within the allowed character set.
+ Example: <literal>TAGS=role=webserver:env=production:berlin</literal>. The same key may not be
+ assigned more than one distinct value: <literal>role=webserver:role=database</literal> is refused
+ (but a key may coexist with the corresponding unparameterized tag, e.g.
+ <literal>role:role=webserver</literal>).</para>
<para>The configured tags may be matched against with the
<varname>ConditionMachineTag=</varname> and <varname>AssertMachineTag=</varname> unit settings, see
ICON_NAME=computer-tablet
CHASSIS=tablet
DEPLOYMENT=production
-TAGS=demo:berlin</programlisting>
+TAGS=demo:berlin:role=webserver</programlisting>
</refsect1>
<refsect1>
negated by prepending an exclamation mark, in which case it is satisfied if none of the configured
tags matches.</para>
+ <para>Tags may be parameterized with a value in the form
+ <literal><replaceable>key</replaceable>=<replaceable>value</replaceable></literal>; the
+ <literal>=</literal> and the value are part of the tag and thus part of the string the pattern is
+ matched against. Hence <literal>ConditionMachineTag=role=webserver</literal> matches the tag
+ <literal>role=webserver</literal> exactly, <literal>ConditionMachineTag=role=*</literal> matches any
+ value assigned to the <literal>role</literal> key, and <literal>ConditionMachineTag=role</literal>
+ (without <literal>=</literal>) does <emphasis>not</emphasis> match <literal>role=webserver</literal>.
+ See
+ <citerefentry><refentrytitle>machine-info</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for the precise syntax of machine tags.</para>
+
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
if (n <= 0 || n >= 256)
return false;
- /* Don't allow "-" and "." as first or last char. (This is load-bearing, we want that "+"/"-" can be
- * used as prefix for adding/removing tags from the list). */
- if (strchr("-.", s[0]) ||
- strchr("-.", s[n-1]))
+ /* Don't allow "-" and "." as first char. (This is load-bearing, we want that "+"/"-" can be used as
+ * prefix for adding/removing tags from the list). */
+ if (strchr("-.=", s[0]))
return false;
- return in_charset(s, ALPHANUMERICAL "-.");
+ /* We allow parameterization of tags, with a "=" as separator */
+ const char *eq = strchr(s, '=');
+ if (eq) {
+ assert(eq > s);
+
+ /* If there is an '=', then make the same restrictions as for the first char on the last char before it */
+ if (strchr("-.", eq[-1]))
+ return false;
+ } else {
+ /* If there's no '=', then make the restriction on the very last character */
+ if (strchr("-.", s[n-1]))
+ return false;
+ }
+
+ return in_charset(s, ALPHANUMERICAL "-.=");
}
bool machine_tag_list_is_valid(char **l) {
if (!machine_tag_is_valid(*i))
return false;
+
+ const char *eq = strchr(*i, '=');
+ if (!eq)
+ continue;
+
+ /* Refuse tags with a common part before the '=', that do no also carry the same value. */
+ size_t np = eq - *i + 1;
+ STRV_FOREACH(j, l) {
+ if (j == i)
+ break;
+
+ if (streq(*i, *j)) /* Fully identical is OK */
+ continue;
+
+ if (strneq(*i, *j, np)) /* Not identical, but same key: refuse */
+ return false;
+ }
}
return true;
if (n > MACHINE_TAGS_MAX)
return -E2BIG;
+ const char *eq = strchr(*i, '=');
+ if (eq) {
+ /* Suppress duplicate assignments */
+ bool skip = false;
+ size_t np = eq - *i + 1;
+ STRV_FOREACH(j, cleaned)
+ if (strneq(*i, *j, np)) {
+ skip = true;
+ break;
+ }
+
+ if (skip)
+ continue;
+ }
+
r = strv_extend(&cleaned, *i);
if (r < 0)
return r;