updatectl: fix feature JSON parsing

The feature JSON payload switched to `id`, `isEnabled`, and `appstream`,
and made `documentation` an array of URLs.
`updatectl` still parsed legacy table-derived names and a single URL.
This broke feature JSON parsing and made output inconsistent.
Features without documentation could also cause a null dereference.

Align `updatectl` with the current schema and preserve all
documentation URLs across sysupdate, D-Bus, and Varlink payloads.
Propagate condition evaluation errors through `condition_test_list()`
and `feature_is_suggested()`, with consistent JSON error handling and
tests.
The `suggested` field is now optional and always a boolean when
present; it is omitted rather than set to an error string when
SuggestOn*= conditions cannot be evaluated.
Add `suggested` metadata and document the field transition.

Follow-up for 3e34ccf6df.
Follow-up for 75a247d4de.
Follow-up for 40e727c93b.

Fixes #43603
This commit is contained in:
dongshengyuan
2026-09-08 16:05:36 +02:00
committed by Zbigniew Jędrzejewski-Szmek
parent 8839a9810e
commit a6f7ded859
10 changed files with 206 additions and 68 deletions
+6
View File
@@ -143,6 +143,12 @@ CHANGES WITH 262 in spe:
"none" returned previously. In the human-readable table output a
hierarchy that is not merged is now shown as "-" instead of "none".
* The JSON payload returned by systemd-sysupdated's DescribeFeature()
method changed the "name" and "enabled" keys to "id" and
"isEnabled", respectively. The "appStream" key is now "appstream",
and the "documentation" key is now an array of strings. Consumers
of this interface must update their JSON parsing accordingly.
Changes in the system and service manager:
* The manager now embeds a basic set of unit files (basic.target,
+12 -6
View File
@@ -322,8 +322,8 @@ node /org/freedesktop/sysupdate1/target/host {
<variablelist>
<varlistentry>
<term><literal>name</literal></term>
<listitem><para>A string containing the feature's name.</para></listitem>
<term><literal>id</literal></term>
<listitem><para>A string containing the feature's ID.</para></listitem>
</varlistentry>
<varlistentry>
@@ -333,18 +333,24 @@ node /org/freedesktop/sysupdate1/target/host {
</varlistentry>
<varlistentry>
<term><literal>enabled</literal></term>
<term><literal>isEnabled</literal></term>
<listitem><para>A boolean indicating whether this feature is enabled.</para></listitem>
</varlistentry>
<varlistentry>
<term><literal>documentationUrl</literal></term>
<listitem><para>An optional string that contains a user-presentable HTTP/HTTPS URL to documentation
<term><literal>suggested</literal></term>
<listitem><para>An optional boolean indicating whether this feature is suggested for enablement. If
omitted, it could not be determined whether the feature is suggested.</para></listitem>
</varlistentry>
<varlistentry>
<term><literal>documentation</literal></term>
<listitem><para>An optional array of strings containing user-presentable HTTP/HTTPS URLs to documentation
about this feature.</para></listitem>
</varlistentry>
<varlistentry>
<term><literal>appstreamUrl</literal></term>
<term><literal>appstream</literal></term>
<listitem><para>An optional string that contains an HTTP/HTTPS URL to an
<ulink url="https://wwww.freedesktop.org/software/appstream/docs/chap-CatalogData.html">appstream
catalog</ulink> XML file containing metadata about this feature.</para></listitem>
+3 -2
View File
@@ -98,8 +98,9 @@
<varlistentry>
<term><varname>Documentation=</varname></term>
<listitem><para>A user-presentable URL to documentation about this component.
This setting supports specifier expansion; see below for details on supported specifiers.</para>
<listitem><para>One or more user-presentable URLs to documentation about this component. This setting may be
specified more than once. If the empty string is assigned to this option, the list is reset, and all prior
assignments have no effect. It supports specifier expansion; see below for details on supported specifiers.</para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
+5 -2
View File
@@ -81,8 +81,11 @@
<varlistentry>
<term><varname>Documentation=</varname></term>
<listitem><para>A user-presentable URL to documentation about this feature.
This setting supports specifier expansion; see below for details on supported specifiers.</para>
<listitem><para>One or more user-presentable URLs to documentation about this feature. This setting may be
specified more than once. If the empty string is assigned to this option, the list is reset, and all prior
assignments have no effect. It supports specifier expansion; see below for details on supported specifiers.</para>
<para>Support for specifying this setting more than once was added in version 262.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
@@ -42,6 +42,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
SD_VARLINK_DEFINE_FIELD(appstream, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Whether the feature is enabled."),
SD_VARLINK_DEFINE_FIELD(isEnabled, SD_VARLINK_BOOL, 0),
SD_VARLINK_FIELD_COMMENT("Whether the feature is suggested for enablement."),
SD_VARLINK_DEFINE_FIELD(suggested, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Array of IDs of the transfers (including currently disabled ones) which are controlled by this feature."),
SD_VARLINK_DEFINE_FIELD(transfers, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY));
+19 -18
View File
@@ -8,6 +8,7 @@
#include "hash-funcs.h"
#include "path-util.h"
#include "string-util.h"
#include "strv.h"
#include "sysupdate-config.h"
#include "sysupdate-feature.h"
#include "sysupdate-util.h"
@@ -19,7 +20,7 @@ static Feature *feature_free(Feature *f) {
free(f->id);
free(f->description);
free(f->documentation);
strv_free(f->documentation);
free(f->appstream);
condition_free_list(f->suggest_on);
@@ -72,22 +73,22 @@ int feature_read_definition(Feature *f, const char *root, const char *path, cons
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed feature filename '%s': %m", filename);
ConfigTableItem table[] = {
{ "Feature", "Description", config_parse_string, 0, &f->description },
{ "Feature", "Documentation", config_parse_url_specifiers, 0, &f->documentation },
{ "Feature", "AppStream", config_parse_url_specifiers, 0, &f->appstream },
{ "Feature", "Enabled", config_parse_bool, 0, &f->enabled },
{ "Feature", "Suggest", config_parse_tristate, 0, &f->suggest },
{ "Feature", "SuggestOnArchitecture", config_parse_condition, CONDITION_ARCHITECTURE, &f->suggest_on },
{ "Feature", "SuggestOnFirmware", config_parse_condition, CONDITION_FIRMWARE, &f->suggest_on },
{ "Feature", "SuggestOnVirtualization", config_parse_condition, CONDITION_VIRTUALIZATION, &f->suggest_on },
{ "Feature", "SuggestOnHost", config_parse_condition, CONDITION_HOST, &f->suggest_on },
{ "Feature", "SuggestOnFraction", config_parse_condition, CONDITION_FRACTION, &f->suggest_on },
{ "Feature", "SuggestOnKernelCommandLine", config_parse_condition, CONDITION_KERNEL_COMMAND_LINE, &f->suggest_on },
{ "Feature", "SuggestOnVersion", config_parse_condition, CONDITION_VERSION, &f->suggest_on },
{ "Feature", "SuggestOnCredential", config_parse_condition, CONDITION_CREDENTIAL, &f->suggest_on },
{ "Feature", "SuggestOnSecurity", config_parse_condition, CONDITION_SECURITY, &f->suggest_on },
{ "Feature", "SuggestOnOSRelease", config_parse_condition, CONDITION_OS_RELEASE, &f->suggest_on },
{ "Feature", "SuggestOnMachineTag", config_parse_condition, CONDITION_MACHINE_TAG, &f->suggest_on },
{ "Feature", "Description", config_parse_string, 0, &f->description },
{ "Feature", "Documentation", config_parse_url_specifiers_many, 0, &f->documentation },
{ "Feature", "AppStream", config_parse_url_specifiers, 0, &f->appstream },
{ "Feature", "Enabled", config_parse_bool, 0, &f->enabled },
{ "Feature", "Suggest", config_parse_tristate, 0, &f->suggest },
{ "Feature", "SuggestOnArchitecture", config_parse_condition, CONDITION_ARCHITECTURE, &f->suggest_on },
{ "Feature", "SuggestOnFirmware", config_parse_condition, CONDITION_FIRMWARE, &f->suggest_on },
{ "Feature", "SuggestOnVirtualization", config_parse_condition, CONDITION_VIRTUALIZATION, &f->suggest_on },
{ "Feature", "SuggestOnHost", config_parse_condition, CONDITION_HOST, &f->suggest_on },
{ "Feature", "SuggestOnFraction", config_parse_condition, CONDITION_FRACTION, &f->suggest_on },
{ "Feature", "SuggestOnKernelCommandLine", config_parse_condition, CONDITION_KERNEL_COMMAND_LINE, &f->suggest_on },
{ "Feature", "SuggestOnVersion", config_parse_condition, CONDITION_VERSION, &f->suggest_on },
{ "Feature", "SuggestOnCredential", config_parse_condition, CONDITION_CREDENTIAL, &f->suggest_on },
{ "Feature", "SuggestOnSecurity", config_parse_condition, CONDITION_SECURITY, &f->suggest_on },
{ "Feature", "SuggestOnOSRelease", config_parse_condition, CONDITION_OS_RELEASE, &f->suggest_on },
{ "Feature", "SuggestOnMachineTag", config_parse_condition, CONDITION_MACHINE_TAG, &f->suggest_on },
{}
};
@@ -111,7 +112,7 @@ int feature_read_definition(Feature *f, const char *root, const char *path, cons
return 0;
}
int feature_is_suggested(Feature *f) {
int feature_is_suggested(const Feature *f) {
assert(f);
if (f->suggest >= 0)
+2 -2
View File
@@ -9,7 +9,7 @@ typedef struct Feature {
char *id;
char *description;
char *documentation;
char **documentation;
char *appstream;
bool enabled;
@@ -27,4 +27,4 @@ extern const struct hash_ops feature_hash_ops;
int feature_read_definition(Feature *f, const char *root, const char *path, const char *const *conf_file_dirs);
int feature_is_suggested(Feature *f);
int feature_is_suggested(const Feature *f);
+31 -12
View File
@@ -2019,6 +2019,8 @@ static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) {
}
}
static int feature_to_json(Context *context, const Feature *f, sd_json_variant **ret);
VERB(verb_features, "features", "[FEATURE]\0", VERB_ANY, 2, 0,
"Show optional features");
static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata) {
@@ -2056,6 +2058,20 @@ static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata
"Optional feature not found: %s",
feature_id);
if (sd_json_format_enabled(arg_json_format_flags)) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
r = feature_to_json(&context, f, &json);
if (r < 0)
return r;
r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
if (r < 0)
return log_error_errno(r, "Failed to print JSON: %m");
return 0;
}
table = table_new_vertical();
if (!table)
return log_oom();
@@ -2092,11 +2108,10 @@ static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata
return table_log_add_error(r);
}
if (f->documentation) {
if (!strv_isempty(f->documentation)) {
r = table_add_many(table,
TABLE_FIELD, "Documentation",
TABLE_STRING, f->documentation,
TABLE_SET_URL, f->documentation);
TABLE_STRV_WRAPPED, f->documentation);
if (r < 0)
return table_log_add_error(r);
}
@@ -2142,8 +2157,7 @@ static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata
r = table_add_many(table,
TABLE_STRING, f->id,
TABLE_STRING, f->description,
TABLE_STRING, f->documentation,
TABLE_SET_URL, f->documentation);
TABLE_STRV_WRAPPED, f->documentation);
if (r < 0)
return table_log_add_error(r);
}
@@ -2248,8 +2262,10 @@ static int context_enable_feature(
Feature *f;
HASHMAP_FOREACH(f, c->features) {
r = feature_is_suggested(f);
if (r < 0)
return log_error_errno(r, "Failed to determine if feature '%s' of component '%s' is suggested: %m", f->id, context_component_display(c));
if (r < 0) {
log_warning_errno(r, "Failed to determine if feature '%s' of component '%s' is suggested, skipping: %m", f->id, context_component_display(c));
continue;
}
if (r == 0) {
log_debug("Feature '%s' of component '%s' is not suggested, skipping.", f->id, context_component_display(c));
continue;
@@ -2404,7 +2420,7 @@ static int verb_enable_feature(int argc, char *argv[], uintptr_t _data, void *us
static int feature_to_json(Context *context, const Feature *f, sd_json_variant **ret) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
_cleanup_strv_free_ char **transfers = NULL;
const char *documentation_strv[2] = { NULL, };
int suggested;
int r;
assert(context);
@@ -2415,17 +2431,20 @@ static int feature_to_json(Context *context, const Feature *f, sd_json_variant *
if (r < 0)
return r;
/* FIXME: Long term we’d like to support an array of documentation, but currently the D-Bus interface
* doesn’t support that and neither do the internals of sysupdate. So just expose 0 or 1 URLs for now. */
documentation_strv[0] = f->documentation;
suggested = feature_is_suggested(f);
if (suggested < 0)
log_debug_errno(suggested,
"Failed to determine whether feature '%s' is suggested, omitting field: %m",
f->id);
r = sd_json_variant_merge_objectbo(
&v,
SD_JSON_BUILD_PAIR_STRING("id", f->id),
JSON_BUILD_PAIR_STRING_NON_EMPTY("description", f->description),
JSON_BUILD_PAIR_STRV_NON_EMPTY("documentation", (char **) documentation_strv),
JSON_BUILD_PAIR_STRV_NON_EMPTY("documentation", f->documentation),
JSON_BUILD_PAIR_STRING_NON_EMPTY("appstream", f->appstream),
SD_JSON_BUILD_PAIR_BOOLEAN("isEnabled", f->enabled),
SD_JSON_BUILD_PAIR_CONDITION(suggested >= 0, "suggested", SD_JSON_BUILD_BOOLEAN(suggested > 0)),
JSON_BUILD_PAIR_STRV_NON_EMPTY("transfers", transfers));
if (r < 0)
return log_oom();
+47 -21
View File
@@ -1386,18 +1386,26 @@ static int verb_vacuum(int argc, char *argv[], uintptr_t _data, void *userdata)
}
typedef struct Feature {
char *name;
char *id;
char *description;
bool enabled;
char *documentation;
int suggested;
char **documentation;
char *appstream;
char **transfers;
} Feature;
#define FEATURE_NULL \
(Feature) { \
.suggested = -1, \
}
static void feature_done(Feature *f) {
assert(f);
f->name = mfree(f->name);
f->id = mfree(f->id);
f->description = mfree(f->description);
f->documentation = mfree(f->documentation);
f->documentation = strv_free(f->documentation);
f->appstream = mfree(f->appstream);
f->transfers = strv_free(f->transfers);
}
@@ -1405,16 +1413,18 @@ static int describe_feature(sd_bus *bus, const char *feature, Feature *ret) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
_cleanup_(feature_done) Feature f = {};
_cleanup_(feature_done) Feature f = FEATURE_NULL;
char *json;
int r;
static const sd_json_dispatch_field dispatch_table[] = {
{ "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Feature, name), SD_JSON_MANDATORY },
{ "description", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Feature, description), 0 },
{ "enabled", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(Feature, enabled), SD_JSON_MANDATORY },
{ "documentationUrl", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Feature, documentation), 0 },
{ "transfers", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(Feature, transfers), 0 },
{ "id", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Feature, id), SD_JSON_MANDATORY },
{ "description", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Feature, description), SD_JSON_NULLABLE },
{ "isEnabled", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(Feature, enabled), SD_JSON_MANDATORY },
{ "suggested", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(Feature, suggested), SD_JSON_NULLABLE },
{ "documentation", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(Feature, documentation), SD_JSON_NULLABLE },
{ "appstream", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Feature, appstream), SD_JSON_NULLABLE },
{ "transfers", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(Feature, transfers), SD_JSON_NULLABLE },
{}
};
@@ -1443,7 +1453,7 @@ static int describe_feature(sd_bus *bus, const char *feature, Feature *ret) {
if (r < 0)
return log_error_errno(r, "Failed to parse JSON: %m");
r = sd_json_dispatch(v, dispatch_table, 0, &f);
r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &f);
if (r < 0)
return log_error_errno(r, "Failed to dispatch JSON: %m");
@@ -1481,15 +1491,15 @@ static int list_features(sd_bus *bus) {
return bus_log_parse_error(r);
STRV_FOREACH(feature, features) {
_cleanup_(feature_done) Feature f = {};
_cleanup_(feature_done) Feature f = FEATURE_NULL;
_cleanup_free_ char *name_link = NULL;
r = describe_feature(bus, *feature, &f);
if (r < 0)
return r;
if (urlify_enabled() && f.documentation) {
name_link = strjoin(f.name, glyph(GLYPH_EXTERNAL_LINK));
if (urlify_enabled() && !strv_isempty(f.documentation)) {
name_link = strjoin(f.id, glyph(GLYPH_EXTERNAL_LINK));
if (!name_link)
return log_oom();
}
@@ -1497,8 +1507,8 @@ static int list_features(sd_bus *bus) {
r = table_add_many(table,
TABLE_BOOLEAN_CHECKMARK, f.enabled,
TABLE_SET_COLOR, ansi_highlight_green_red(f.enabled),
TABLE_STRING, name_link ?: f.name,
TABLE_SET_URL, f.documentation,
TABLE_STRING, name_link ?: f.id,
TABLE_SET_URL, strv_isempty(f.documentation) ? NULL : f.documentation[0],
TABLE_STRING, f.description);
if (r < 0)
return table_log_add_error(r);
@@ -1512,7 +1522,7 @@ VERB(verb_features, "features", "[FEATURE]\0", VERB_ANY, 2, VERB_ONLINE_ONLY,
static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata) {
sd_bus *bus = ASSERT_PTR(userdata);
_cleanup_(table_unrefp) Table *table = NULL;
_cleanup_(feature_done) Feature f = {};
_cleanup_(feature_done) Feature f = FEATURE_NULL;
int r;
if (argc == 1)
@@ -1528,23 +1538,39 @@ static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata
r = table_add_many(table,
TABLE_FIELD, "Name",
TABLE_STRING, f.name,
TABLE_STRING, f.id,
TABLE_FIELD, "Enabled",
TABLE_BOOLEAN, f.enabled);
if (r < 0)
return table_log_add_error(r);
if (f.suggested >= 0) {
r = table_add_many(table,
TABLE_FIELD, "Suggested",
TABLE_BOOLEAN, f.suggested);
if (r < 0)
return table_log_add_error(r);
}
if (f.description) {
r = table_add_many(table, TABLE_FIELD, "Description", TABLE_STRING, f.description);
if (r < 0)
return table_log_add_error(r);
}
if (f.documentation) {
if (!strv_isempty(f.documentation)) {
r = table_add_many(table,
TABLE_FIELD, "Documentation",
TABLE_STRING, f.documentation,
TABLE_SET_URL, f.documentation);
TABLE_STRV_WRAPPED, f.documentation);
if (r < 0)
return table_log_add_error(r);
}
if (f.appstream) {
r = table_add_many(table,
TABLE_FIELD, "AppStream",
TABLE_STRING, f.appstream,
TABLE_SET_URL, f.appstream);
if (r < 0)
return table_log_add_error(r);
}
+79 -5
View File
@@ -254,7 +254,10 @@ verify_version_current() {
verify_object_fields() {
local updatectl_output="${1:?}"
[[ "${updatectl_output}" != *"Unrecognized object field"* ]]
[[ "${updatectl_output}" != *"Unexpected object field"* &&
"${updatectl_output}" != *"Missing object field"* &&
"${updatectl_output}" != *"Failed to dispatch JSON"* &&
"${updatectl_output}" != *"Failed to parse JSON"* ]]
}
for sector_size in "${SECTOR_SIZES[@]}"; do
@@ -397,6 +400,26 @@ EOF
cat >"$CONFIGDIR/optional.feature" <<EOF
[Feature]
Description=Optional Feature
Documentation=https://example.com/optional
Documentation=https://example.com/optional-more
AppStream=https://example.com/optional.appstream.xml
EOF
cat >"$CONFIGDIR/undocumented.feature" <<EOF
[Feature]
Description=Feature Without Documentation
EOF
cat >"$CONFIGDIR/malformed.feature" <<EOF
[Feature]
Description=Feature With Invalid Suggest Condition
SuggestOnFirmware=smbios-field(malformed)
EOF
cat >"$CONFIGDIR/suggested.feature" <<EOF
[Feature]
Description=Suggested Feature
Suggest=yes
EOF
cat >"$CONFIGDIR/99-optional.transfer" <<EOF
@@ -476,6 +499,31 @@ EOF
exit 1
fi
feature_json="$("$SYSUPDATE" --json=short features optional)"
jq -e '.id == "optional"' <<<"$feature_json" >/dev/null
jq -e '
.documentation == [
"https://example.com/optional",
"https://example.com/optional-more"
]
' <<<"$feature_json" >/dev/null
jq -e '.isEnabled == false' <<<"$feature_json" >/dev/null
jq -e '.suggested == false' <<<"$feature_json" >/dev/null
jq -e '.appstream == "https://example.com/optional.appstream.xml"' <<<"$feature_json" >/dev/null
suggested_json="$("$SYSUPDATE" --json=short features suggested)"
jq -e '.id == "suggested" and .suggested == true' <<<"$suggested_json" >/dev/null
undocumented_json="$("$SYSUPDATE" --json=short features undocumented)"
jq -e '.id == "undocumented" and (.documentation == null)' <<<"$undocumented_json" >/dev/null
"$SYSUPDATE" features undocumented | grep -F "Feature Without Documentation" >/dev/null
malformed_json="$("$SYSUPDATE" --json=short features malformed)"
jq -e '.id == "malformed" and (has("suggested") | not)' <<<"$malformed_json" >/dev/null
"$SYSUPDATE" features malformed | grep -F "error (Invalid argument)" >/dev/null
all_features_output="$("$SYSUPDATE" features)"
grep -F "malformed" <<<"$all_features_output" >/dev/null
test ! -f "$WORKDIR/xbootldr/EFI/Linux/uki_v5.efi.extra.d/optional.efi"
mkdir "$CONFIGDIR/optional.feature.d"
echo -e "[Feature]\nEnabled=true" > "$CONFIGDIR/optional.feature.d/enable.conf"
@@ -521,9 +569,25 @@ EOF
if [[ -x "$UPDATECTL" ]]; then
mkdir -p /run/sysupdate.test.d/
cp "$CONFIGDIR/01-first.transfer" /run/sysupdate.test.d/01-first.transfer
verify_object_fields "$("$UPDATECTL" list 2>&1)"
verify_object_fields "$("$UPDATECTL" list host 2>&1)"
verify_object_fields "$("$UPDATECTL" list host@v6 2>&1)"
updatectl_output="$("$UPDATECTL" list 2>&1)"
verify_object_fields "$updatectl_output"
updatectl_output="$("$UPDATECTL" list host 2>&1)"
verify_object_fields "$updatectl_output"
updatectl_output="$("$UPDATECTL" list host@v6 2>&1)"
verify_object_fields "$updatectl_output"
features_output="$("$UPDATECTL" features)"
grep "optional" <<<"$features_output" >/dev/null
grep "undocumented" <<<"$features_output" >/dev/null
feature_output="$("$UPDATECTL" features optional)"
grep "Optional Feature" <<<"$feature_output" >/dev/null
grep "Suggested: no" <<<"$feature_output" >/dev/null
grep "https://example.com/optional-more" <<<"$feature_output" >/dev/null
grep "https://example.com/optional.appstream.xml" <<<"$feature_output" >/dev/null
grep -F "99-optional" <<<"$feature_output" >/dev/null
grep "malformed" <<<"$features_output" >/dev/null
verify_object_fields "$features_output"
malformed_feature_output="$("$UPDATECTL" features malformed)"
(! grep -F "Suggested:" <<<"$malformed_feature_output")
"$UPDATECTL" check
rm -r /run/sysupdate.test.d
fi
@@ -1950,7 +2014,8 @@ compfeat_source v1
mkdir -p /run/sysupdate.d
compfeat_transfer /run/sysupdate.d/01-base.transfer base "$CF/target-default"
# feata: suggested (Suggest=yes); featb: not suggested (Suggest=no);
# featc: suggested on a machine tag we will set below.
# featc: suggested on a machine tag we will set below; featd: malformed
# suggestion condition, which should be skipped without aborting the operation.
cat >/run/sysupdate.d/feata.feature <<EOF
[Feature]
Suggest=yes
@@ -1963,18 +2028,24 @@ cat >/run/sysupdate.d/featc.feature <<EOF
[Feature]
SuggestOnMachineTag=sysupdate-test-tag
EOF
cat >/run/sysupdate.d/featd.feature <<EOF
[Feature]
SuggestOnFirmware=smbios-field(malformed)
EOF
# --feature-all operates on every known feature.
"$SYSUPDATE" enable-feature --feature-all
assert_dropin "$(feat_enable_dropin_default feata)" yes
assert_dropin "$(feat_enable_dropin_default featb)" yes
assert_dropin "$(feat_enable_dropin_default featc)" yes
assert_dropin "$(feat_enable_dropin_default featd)" yes
# ... and so does 'disable-feature --feature-all', in the other direction.
"$SYSUPDATE" disable-feature --feature-all
assert_dropin "$(feat_enable_dropin_default feata)" no
assert_dropin "$(feat_enable_dropin_default featb)" no
assert_dropin "$(feat_enable_dropin_default featc)" no
assert_dropin "$(feat_enable_dropin_default featd)" no
# --feature-suggested (no machine tag): only the Suggest=yes feature is picked.
rm -rf /etc/sysupdate.d
@@ -1983,6 +2054,7 @@ set_machine_tags unrelated
assert_dropin "$(feat_enable_dropin_default feata)" yes
test ! -e "$(feat_enable_dropin_default featb)"
test ! -e "$(feat_enable_dropin_default featc)"
test ! -e "$(feat_enable_dropin_default featd)"
# --feature-suggested with the machine tag set: feata + featc are picked.
rm -rf /etc/sysupdate.d
@@ -1991,6 +2063,7 @@ set_machine_tags sysupdate-test-tag
assert_dropin "$(feat_enable_dropin_default feata)" yes
assert_dropin "$(feat_enable_dropin_default featc)" yes
test ! -e "$(feat_enable_dropin_default featb)"
test ! -e "$(feat_enable_dropin_default featd)"
# 'disable-feature --feature-suggested' selects the same features as the enable
# above, i.e. it undoes it and leaves the non-suggested featb untouched.
@@ -1998,6 +2071,7 @@ test ! -e "$(feat_enable_dropin_default featb)"
assert_dropin "$(feat_enable_dropin_default feata)" no
assert_dropin "$(feat_enable_dropin_default featc)" no
test ! -e "$(feat_enable_dropin_default featb)"
test ! -e "$(feat_enable_dropin_default featd)"
# The same two step reconciliation for features: disable all of them, then
# enable the suggested ones, leaving the non-suggested featb explicitly off.