And add integration tests for it using `jq`.
SD_VARLINK_FIELD_COMMENT("Name of the target, unique within a class."),
SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
+static SD_VARLINK_DEFINE_STRUCT_TYPE(
+ Feature,
+ SD_VARLINK_FIELD_COMMENT("Identifier for the feature."),
+ SD_VARLINK_DEFINE_FIELD(id, SD_VARLINK_STRING, 0),
+ SD_VARLINK_FIELD_COMMENT("A short human readable description of the feature."),
+ SD_VARLINK_DEFINE_FIELD(description, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("A URL to documentation for the feature."),
+ SD_VARLINK_DEFINE_FIELD(documentation, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("A URL to an AppStream catalog XML file describing the feature."),
+ 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("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));
+
+static SD_VARLINK_DEFINE_METHOD(
+ ListFeatures,
+ SD_VARLINK_FIELD_COMMENT("Target to list features for."),
+ SD_VARLINK_DEFINE_INPUT_BY_TYPE(target, TargetIdentifier, 0),
+ SD_VARLINK_FIELD_COMMENT("The configured features."),
+ SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(features, Feature, SD_VARLINK_ARRAY));
+
static SD_VARLINK_DEFINE_METHOD(
CheckNew,
SD_VARLINK_FIELD_COMMENT("Target to check for updates for."),
SD_VARLINK_INTERFACE_COMMENT("APIs to manage system updates"),
/* Methods */
+ SD_VARLINK_SYMBOL_COMMENT("Show optional features"),
+ &vl_method_ListFeatures,
SD_VARLINK_SYMBOL_COMMENT("Check if there’s a new version available"),
&vl_method_CheckNew,
&vl_type_TargetClass,
SD_VARLINK_SYMBOL_COMMENT("Identifier for a component of the system (i.e. the host itself, a sysext, a confext, etc.) that can be updated by systemd-sysupdate(8)."),
&vl_type_TargetIdentifier,
+ SD_VARLINK_SYMBOL_COMMENT("Type containing a configured sysupdate feature."),
+ &vl_type_Feature,
/* Errors */
SD_VARLINK_SYMBOL_COMMENT("Error indicating that no update is currently available to update to."),
}
}
+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;
+ int r;
+
+ assert(context);
+ assert(f);
+ assert(ret);
+
+ r = context_get_transfers_for_feature(context, f->id, &transfers);
+ if (r < 0)
+ return r;
+
+ 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_STRING_NON_EMPTY("documentation", f->documentation),
+ JSON_BUILD_PAIR_STRING_NON_EMPTY("appstream", f->appstream),
+ SD_JSON_BUILD_PAIR_BOOLEAN("isEnabled", f->enabled),
+ JSON_BUILD_PAIR_STRV_NON_EMPTY("transfers", transfers));
+ if (r < 0)
+ return log_oom();
+
+ *ret = TAKE_PTR(v);
+ return 1;
+}
+
+static int vl_method_list_features(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
+ _cleanup_(context_done) Context context = CONTEXT_NULL;
+ Feature *f;
+ int r;
+
+ assert(link);
+
+ static const sd_json_dispatch_field dispatch_table[] = {
+ { "target", SD_JSON_VARIANT_OBJECT, dispatch_target_identifier, voffsetof(context, target_identifier), SD_JSON_MANDATORY },
+ {},
+ };
+
+ r = sd_varlink_dispatch(link, parameters, dispatch_table, &context);
+ if (r != 0)
+ return r;
+
+ /* Listing features doesn’t require a polkit check */
+
+ if (getenv_bool("SYSTEMD_SYSUPDATE_NO_VERIFY") > 0)
+ context.verify = 0;
+
+ /* ListFeatures is always online */
+ context.offline = false;
+
+ r = context_load_online_from_target(
+ &context,
+ PROCESS_IMAGE_READ_ONLY,
+ READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
+ if (r == -ENOENT)
+ return sd_varlink_error(link, "io.systemd.SysUpdate.NoSuchTarget", NULL);
+ if (r < 0)
+ return r;
+
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *l = NULL;
+
+ HASHMAP_FOREACH(f, context.features) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+
+ r = feature_to_json(&context, f, &v);
+ if (r < 0)
+ return r;
+
+ r = sd_json_variant_append_array(&l, v);
+ if (r < 0)
+ return r;
+ }
+
+ if (!l) {
+ r = sd_json_variant_new_array(&l, NULL, 0);
+ if (r < 0)
+ return r;
+ }
+
+ return sd_varlink_replybo(link,
+ SD_JSON_BUILD_PAIR_VARIANT("features", l));
+}
+
VERB_NOARG(verb_check_new, "check-new",
"Check if there's a new version available");
static int verb_check_new(int argc, char *argv[], uintptr_t _data, void *userdata) {
r = sd_varlink_server_bind_method_many(
varlink_server,
- "io.systemd.SysUpdate.CheckNew", vl_method_check_new);
+ "io.systemd.SysUpdate.CheckNew", vl_method_check_new,
+ "io.systemd.SysUpdate.ListFeatures", vl_method_list_features);
if (r < 0)
return log_error_errno(r, "Failed to bind Varlink method: %m");
verify_version_current "$blockdev" "$sector_size" v5 2
# Now let's try enabling an optional feature
- "$SYSUPDATE" features | grep "optional"
- "$SYSUPDATE" features optional | grep "99-optional"
+ if [[ "$client" == "sysupdate-cli" ]]; then
+ "$SYSUPDATE" features | grep "optional"
+ "$SYSUPDATE" features optional | grep "99-optional"
+ elif [[ "$client" == "varlink" ]]; then
+ [[ $(varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.ListFeatures '{"target":{"class":"host"}}' | jq -r '.features[] | select(.id=="optional") | .description') == "Optional Feature" ]]
+ varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.ListFeatures '{"target":{"class":"host"}}' | jq -r '.features[] | select(.id=="optional") | .transfers' | grep "99-optional"
+ else
+ exit 1
+ fi
+
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"
MatchPattern=tiny-@v.bin
InstancesMax=1
EOF
+
"$SYSUPDATE" --verify=no update
cmp "$WORKDIR/source/tiny-v1.bin" "$WORKDIR/blobs/tiny-v1.bin"
+
+# Test that listing features when none are configured gives an empty list.
+"$SYSUPDATE" features |& grep "No features." >/dev/null
+[[ $(varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.ListFeatures '{"target":{"class":"host"}}' | jq -r '.features') == "[]" ]]
+
+# Cleanup
rm "$CONFIGDIR/01-tiny-url.transfer"
rm "$WORKDIR/source/tiny-v1.bin"
rm "$WORKDIR/source/SHA256SUMS"