]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysupdate: Add varlink ListFeatures() method
authorPhilip Withnall <pwithnall@gnome.org>
Mon, 1 Jun 2026 17:35:54 +0000 (18:35 +0100)
committerPhilip Withnall <pwithnall@gnome.org>
Tue, 14 Jul 2026 13:57:13 +0000 (14:57 +0100)
And add integration tests for it using `jq`.

src/shared/varlink-io.systemd.SysUpdate.c
src/sysupdate/sysupdate.c
test/units/TEST-72-SYSUPDATE.sh

index e80142f9c510fcb3873e11a5f17c752b905ee678..94a7180a5098167e9144ebf35c60a3a776009ca3 100644 (file)
@@ -25,6 +25,28 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 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."),
@@ -42,6 +64,8 @@ SD_VARLINK_DEFINE_INTERFACE(
                 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,
 
@@ -50,6 +74,8 @@ SD_VARLINK_DEFINE_INTERFACE(
                 &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."),
index 19fd136931aac9f2ca420ea5c8fe37a3b0de4fbe..9ed5d8db4644c316c2129d7ffd39a29d7fd1daea 100644 (file)
@@ -2241,6 +2241,91 @@ 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;
+        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) {
@@ -3182,7 +3267,8 @@ static int vl_server(void) {
 
         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");
 
index d3a8a4bc61ff96bedd804b2af41f2ba3d397492f..6e3e3603b0cb821c7733cb13257f970c08736f30 100755 (executable)
@@ -461,8 +461,16 @@ EOF
     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"
@@ -674,8 +682,15 @@ Path=$WORKDIR/blobs
 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"