updatectl: fix feature JSON parsing (#43617)
## Summary `updatectl features` failed because it expected the old feature JSON fields, while `systemd-sysupdate` now returns `id`, `isEnabled`, and `documentation` as an array. This change: - Aligns `updatectl` with the current feature JSON schema. - Unifies JSON output between `systemd-sysupdate`, D-Bus, and Varlink. - Adds support for multiple `Documentation=` entries for Features and Components. - Exposes and parses `appstream` and `suggested` metadata. - Propagates condition-evaluation errors through `condition_test_list()` and `feature_is_suggested()`. - Reports suggestion-evaluation failures instead of silently treating them as `false`. - Improves JSON dump error handling. - Expands `TEST-72-SYSUPDATE` coverage and fixes its unknown-field assertion. - Documents the `DescribeFeature()` JSON field transition in `NEWS`. - Keeps the existing Component name list for `sysupdated` compatibility. The `name`/`enabled` to `id`/`isEnabled` transition is intentional and documented as a v262 payload change. Fixes #43603
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -125,11 +125,11 @@ static int verify_conditions(char **lines, RuntimeScope scope, const char *unit,
|
||||
}
|
||||
|
||||
condition_test_logger_t logger = arg_quiet ? NULL : log_helper;
|
||||
r = condition_test_list(u->asserts, environ, assert_type_to_string, logger, u);
|
||||
r = condition_test_list(u->asserts, environ, assert_type_to_string, logger, u) > 0;
|
||||
if (u->asserts)
|
||||
log_full(arg_quiet ? LOG_DEBUG : LOG_NOTICE, "Asserts %s.", r > 0 ? "succeeded" : "failed");
|
||||
|
||||
q = condition_test_list(u->conditions, environ, condition_type_to_string, logger, u);
|
||||
q = condition_test_list(u->conditions, environ, condition_type_to_string, logger, u) > 0;
|
||||
if (u->conditions)
|
||||
log_full(arg_quiet ? LOG_DEBUG : LOG_NOTICE, "Conditions %s.", q > 0 ? "succeeded" : "failed");
|
||||
|
||||
|
||||
+2
-2
@@ -1819,7 +1819,7 @@ static bool unit_test_condition(Unit *u) {
|
||||
env,
|
||||
condition_type_to_string,
|
||||
log_unit_internal,
|
||||
u);
|
||||
u) > 0;
|
||||
|
||||
unit_add_to_dbus_queue(u);
|
||||
return u->condition_result;
|
||||
@@ -1843,7 +1843,7 @@ static bool unit_test_assert(Unit *u) {
|
||||
env,
|
||||
assert_type_to_string,
|
||||
log_unit_internal,
|
||||
u);
|
||||
u) > 0;
|
||||
|
||||
unit_add_to_dbus_queue(u);
|
||||
return u->assert_result;
|
||||
|
||||
+25
-9
@@ -28,6 +28,7 @@
|
||||
#include "efivars.h"
|
||||
#include "env-file.h"
|
||||
#include "env-util.h"
|
||||
#include "errno-util.h"
|
||||
#include "extract-word.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
@@ -1420,7 +1421,7 @@ int condition_test(Condition *c, char **env) {
|
||||
return condition_test_impl(c, env, table);
|
||||
}
|
||||
|
||||
static bool condition_test_list_impl(
|
||||
static int condition_test_list_impl(
|
||||
Condition *first,
|
||||
char **env,
|
||||
condition_to_string_t to_string,
|
||||
@@ -1428,7 +1429,8 @@ static bool condition_test_list_impl(
|
||||
condition_test_func_t tester,
|
||||
void *userdata) {
|
||||
|
||||
int triggered = -1;
|
||||
int non_trigger_error = 0, trigger_error = 0;
|
||||
bool has_trigger = false, trigger_success = false;
|
||||
|
||||
/* If the condition list is empty, then it is true */
|
||||
if (!first)
|
||||
@@ -1440,12 +1442,14 @@ static bool condition_test_list_impl(
|
||||
LIST_FOREACH(conditions, c, first) {
|
||||
int r;
|
||||
|
||||
has_trigger = has_trigger || c->trigger;
|
||||
|
||||
r = tester(c, env);
|
||||
|
||||
if (logger) {
|
||||
if (r < 0)
|
||||
logger(userdata, LOG_WARNING, r, PROJECT_FILE, __LINE__, __func__,
|
||||
"Couldn't determine result for %s=%s%s%s, assuming failed: %m",
|
||||
"Couldn't determine result for %s=%s%s%s: %m",
|
||||
to_string(c->type),
|
||||
c->trigger ? "|" : "",
|
||||
c->negate ? "!" : "",
|
||||
@@ -1460,14 +1464,26 @@ static bool condition_test_list_impl(
|
||||
condition_result_to_string(c->result));
|
||||
}
|
||||
|
||||
if (!c->trigger && r <= 0)
|
||||
if (r < 0) {
|
||||
if (c->trigger)
|
||||
RET_GATHER(trigger_error, r);
|
||||
else
|
||||
RET_GATHER(non_trigger_error, r);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!c->trigger && r == 0)
|
||||
return false;
|
||||
|
||||
if (c->trigger && triggered <= 0)
|
||||
triggered = r > 0;
|
||||
if (c->trigger && r > 0)
|
||||
trigger_success = true;
|
||||
}
|
||||
|
||||
return triggered != 0;
|
||||
if (!has_trigger || trigger_success)
|
||||
return non_trigger_error < 0 ? non_trigger_error : true;
|
||||
|
||||
return trigger_error < 0 ? trigger_error : false;
|
||||
}
|
||||
|
||||
bool condition_test_list_net(
|
||||
@@ -1477,10 +1493,10 @@ bool condition_test_list_net(
|
||||
condition_test_logger_t logger,
|
||||
void *userdata) {
|
||||
|
||||
return condition_test_list_impl(first, env, to_string, logger, condition_test_net, userdata);
|
||||
return condition_test_list_impl(first, env, to_string, logger, condition_test_net, userdata) > 0;
|
||||
}
|
||||
|
||||
bool condition_test_list(
|
||||
int condition_test_list(
|
||||
Condition *first,
|
||||
char **env,
|
||||
condition_to_string_t to_string,
|
||||
|
||||
@@ -86,7 +86,7 @@ int condition_test(Condition *c, char **env);
|
||||
typedef int (*condition_test_logger_t)(void *userdata, int level, int error, const char *file, int line, const char *func, const char *format, ...) _printf_(7, 8);
|
||||
typedef const char* (*condition_to_string_t)(ConditionType t) _const_;
|
||||
bool condition_test_list_net(Condition *first, char **env, condition_to_string_t to_string, condition_test_logger_t logger, void *userdata);
|
||||
bool condition_test_list(Condition *first, char **env, condition_to_string_t to_string, condition_test_logger_t logger, void *userdata);
|
||||
int condition_test_list(Condition *first, char **env, condition_to_string_t to_string, condition_test_logger_t logger, void *userdata);
|
||||
|
||||
void condition_dump(Condition *c, FILE *f, const char *prefix, condition_to_string_t to_string);
|
||||
void condition_dump_list(Condition *first, FILE *f, const char *prefix, condition_to_string_t to_string);
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
+35
-18
@@ -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();
|
||||
@@ -3073,14 +3092,11 @@ static int verb_components(int argc, char *argv[], uintptr_t _data, void *userda
|
||||
if (r < 0)
|
||||
return table_log_add_error(r);
|
||||
|
||||
const char *doc = cc.component_documentation ? cc.component_documentation[0] : NULL;
|
||||
|
||||
r = table_add_many(
|
||||
t,
|
||||
TABLE_STRING, *i,
|
||||
TABLE_STRING, cc.component_description,
|
||||
TABLE_STRING, doc,
|
||||
TABLE_SET_URL, doc);
|
||||
TABLE_STRV_WRAPPED, cc.component_documentation);
|
||||
if (r < 0)
|
||||
return table_log_add_error(r);
|
||||
}
|
||||
@@ -3089,8 +3105,9 @@ static int verb_components(int argc, char *argv[], uintptr_t _data, void *userda
|
||||
} else {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
|
||||
|
||||
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_BOOLEAN("default", has_default_component),
|
||||
SD_JSON_BUILD_PAIR_STRV("components", component_names));
|
||||
r = sd_json_buildo(&json,
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("default", has_default_component),
|
||||
SD_JSON_BUILD_PAIR_STRV("components", component_names));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create JSON: %m");
|
||||
|
||||
|
||||
+47
-21
@@ -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);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,78 @@
|
||||
#include "user-util.h"
|
||||
#include "virt.h"
|
||||
|
||||
TEST(condition_test_list) {
|
||||
Condition *condition, *sibling;
|
||||
Architecture arch, other_arch;
|
||||
|
||||
ASSERT_OK(arch = uname_architecture());
|
||||
|
||||
ASSERT_OK_POSITIVE(condition_test_list(NULL, environ, NULL, NULL, NULL));
|
||||
|
||||
ASSERT_NOT_NULL((condition = condition_new(CONDITION_FIRMWARE, "smbios-field(malformed)", false, false)));
|
||||
ASSERT_ERROR(condition_test_list(condition, environ, NULL, NULL, NULL), EINVAL);
|
||||
condition_free(condition);
|
||||
|
||||
ASSERT_NOT_NULL((condition = condition_new(CONDITION_FIRMWARE, "smbios-field(malformed)", true, false)));
|
||||
ASSERT_NOT_NULL((sibling = condition_new(CONDITION_ARCHITECTURE, architecture_to_string(arch), true, false)));
|
||||
LIST_APPEND(conditions, condition, sibling);
|
||||
ASSERT_OK_POSITIVE(condition_test_list(condition, environ, NULL, NULL, NULL));
|
||||
condition_free_list(condition);
|
||||
|
||||
ASSERT_NOT_NULL((condition = condition_new(CONDITION_ARCHITECTURE, architecture_to_string(arch), true, false)));
|
||||
ASSERT_NOT_NULL((sibling = condition_new(CONDITION_FIRMWARE, "smbios-field(malformed)", true, false)));
|
||||
LIST_APPEND(conditions, condition, sibling);
|
||||
ASSERT_OK_POSITIVE(condition_test_list(condition, environ, NULL, NULL, NULL));
|
||||
condition_free_list(condition);
|
||||
|
||||
ASSERT_NOT_NULL((condition = condition_new(CONDITION_FIRMWARE, "smbios-field(malformed)", true, false)));
|
||||
ASSERT_ERROR(condition_test_list(condition, environ, NULL, NULL, NULL), EINVAL);
|
||||
condition_free(condition);
|
||||
|
||||
ASSERT_NOT_NULL((condition = condition_new(CONDITION_FIRMWARE, "smbios-field(malformed)", true, false)));
|
||||
ASSERT_NOT_NULL((sibling = condition_new(CONDITION_PATH_EXISTS, "/thiscertainlywontexist", false, false)));
|
||||
LIST_APPEND(conditions, condition, sibling);
|
||||
ASSERT_OK_ZERO(condition_test_list(condition, environ, NULL, NULL, NULL));
|
||||
condition_free_list(condition);
|
||||
|
||||
ASSERT_NOT_NULL((condition = condition_new(CONDITION_FIRMWARE, "smbios-field(malformed)", true, false)));
|
||||
ASSERT_NOT_NULL((sibling = condition_new(CONDITION_PATH_EXISTS, "/bin/sh", false, false)));
|
||||
LIST_APPEND(conditions, condition, sibling);
|
||||
ASSERT_ERROR(condition_test_list(condition, environ, NULL, NULL, NULL), EINVAL);
|
||||
condition_free_list(condition);
|
||||
|
||||
ASSERT_NOT_NULL((condition = condition_new(CONDITION_PATH_EXISTS, "/thiscertainlywontexist", false, false)));
|
||||
ASSERT_NOT_NULL((sibling = condition_new(CONDITION_FIRMWARE, "smbios-field(malformed)", false, false)));
|
||||
LIST_APPEND(conditions, condition, sibling);
|
||||
ASSERT_OK_ZERO(condition_test_list(condition, environ, NULL, NULL, NULL));
|
||||
condition_free_list(condition);
|
||||
|
||||
ASSERT_NOT_NULL((condition = condition_new(CONDITION_FIRMWARE, "smbios-field(malformed)", false, false)));
|
||||
ASSERT_NOT_NULL((sibling = condition_new(CONDITION_PATH_EXISTS, "/thiscertainlywontexist", false, false)));
|
||||
LIST_APPEND(conditions, condition, sibling);
|
||||
ASSERT_OK_ZERO(condition_test_list(condition, environ, NULL, NULL, NULL));
|
||||
condition_free_list(condition);
|
||||
|
||||
ASSERT_NOT_NULL((condition = condition_new(CONDITION_PATH_EXISTS, "/bin/sh", false, false)));
|
||||
ASSERT_NOT_NULL((sibling = condition_new(CONDITION_ARCHITECTURE, architecture_to_string(arch), false, false)));
|
||||
LIST_APPEND(conditions, condition, sibling);
|
||||
ASSERT_OK_POSITIVE(condition_test_list(condition, environ, NULL, NULL, NULL));
|
||||
condition_free_list(condition);
|
||||
|
||||
ASSERT_NOT_NULL((condition = condition_new(CONDITION_FIRMWARE, "smbios-field(malformed)", false, false)));
|
||||
ASSERT_NOT_NULL((sibling = condition_new(CONDITION_ARCHITECTURE, architecture_to_string(arch), true, false)));
|
||||
LIST_APPEND(conditions, condition, sibling);
|
||||
ASSERT_ERROR(condition_test_list(condition, environ, NULL, NULL, NULL), EINVAL);
|
||||
condition_free_list(condition);
|
||||
|
||||
other_arch = arch == ARCHITECTURE_X86_64 ? ARCHITECTURE_X86 : ARCHITECTURE_X86_64;
|
||||
ASSERT_NOT_NULL((condition = condition_new(CONDITION_FIRMWARE, "smbios-field(malformed)", false, false)));
|
||||
ASSERT_NOT_NULL((sibling = condition_new(CONDITION_ARCHITECTURE, architecture_to_string(other_arch), true, false)));
|
||||
LIST_APPEND(conditions, condition, sibling);
|
||||
ASSERT_OK_ZERO(condition_test_list(condition, environ, NULL, NULL, NULL));
|
||||
condition_free_list(condition);
|
||||
}
|
||||
|
||||
TEST(condition_test_path) {
|
||||
Condition *condition;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -635,6 +699,11 @@ done
|
||||
# that a ‘default’ component is only listed by sysupdate if it’s fully configured
|
||||
mv "$CONFIGDIR" "$CONFIGDIR.backup"
|
||||
mkdir -p /run/sysupdate.some-component.d
|
||||
cat >/run/sysupdate.some-component.component <<EOF
|
||||
[Component]
|
||||
Documentation=https://example.com/some-component
|
||||
Documentation=https://example.com/some-component-more
|
||||
EOF
|
||||
tee /run/sysupdate.some-component.d/portable.transfer << EOF
|
||||
[Transfer]
|
||||
ChangeLog=https://example.com/changelog/@v
|
||||
@@ -652,6 +721,9 @@ MatchPattern=some-component_@v
|
||||
CurrentSymlink=some-component
|
||||
EOF
|
||||
"$SYSUPDATE" --json=short components | grep -F '{"default":false,"components":["some-component"]}' >/dev/null
|
||||
component_output="$("$SYSUPDATE" components)"
|
||||
grep -F "https://example.com/some-component" <<<"$component_output" >/dev/null
|
||||
grep -F "https://example.com/some-component-more" <<<"$component_output" >/dev/null
|
||||
varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.ListTargets | jq -e '.targets | all(.id.class != "host")' >/dev/null
|
||||
mkdir /run/sysupdate.d
|
||||
"$SYSUPDATE" --json=short components | grep -F '{"default":false,"components":["some-component"]}' >/dev/null
|
||||
@@ -670,6 +742,7 @@ varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.ListTargets | jq -e '.tar
|
||||
# Clean up regression test
|
||||
rmdir /run/sysupdate.d
|
||||
rm -rf /run/sysupdate.some-component.d
|
||||
rm -f /run/sysupdate.some-component.component
|
||||
mv "$CONFIGDIR.backup" "$CONFIGDIR"
|
||||
|
||||
# Make sure the processing of compressed streams still handles uncompressed streams shorter than
|
||||
@@ -1950,7 +2023,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 +2037,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 +2063,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 +2072,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 +2080,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.
|
||||
|
||||
Reference in New Issue
Block a user