]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
imds: expose instance metadata as an io.systemd.Metrics provider
authorLennart Poettering <lennart@amutable.com>
Sat, 30 May 2026 09:02:47 +0000 (11:02 +0200)
committerLennart Poettering <lennart@amutable.com>
Mon, 22 Jun 2026 20:03:12 +0000 (22:03 +0200)
When systemd-imds is invoked as a Varlink service (via the new
systemd-imds-metrics.socket), it now acts as an io.systemd.Metrics
provider for systemd-report. It connects to systemd-imdsd over the
existing io.systemd.InstanceMetadata interface to acquire the real
data and re-exposes the detected cloud vendor plus the well-known
hostname, region, zone and public IPv4/IPv6 fields as metrics in the
io.systemd.InstanceMetadata.* namespace.

The metrics logic lives entirely on the client side
(imds-tool-metrics.c); systemd-imdsd is unchanged. Each metric is
acquired on demand with a blocking call to the daemon, benefiting from
its local cache. Fields that are unset or unsupported by the vendor are
simply omitted.

The metrics socket is statically enabled into sockets.target.wants/.

man/systemd-imds.xml
src/imds/imds-generator.c
src/imds/imds-tool-metrics.c [new file with mode: 0644]
src/imds/imds-tool-metrics.h [new file with mode: 0644]
src/imds/imds-tool.c
src/imds/imds-tool.h [new file with mode: 0644]
src/imds/meson.build
units/meson.build
units/systemd-imds-metrics.socket [new file with mode: 0644]
units/systemd-imds-metrics@.service.in [new file with mode: 0644]

index 3980c7560c351585a119450750657be17096dc37..b8b314ccbd6666e45adf772544b044823957d942 100644 (file)
       fields are imported into the local credential store, where they are used to configure and parameterize
       the system. For details see below.</para></listitem>
     </itemizedlist>
+
+    <para>In addition, when invoked as a Varlink service (i.e. via socket activation through
+    <filename>systemd-imds-metrics.socket</filename>), <command>systemd-imds</command> acts as an
+    <constant>io.systemd.Metrics</constant> provider for
+    <citerefentry><refentrytitle>systemd-report</refentrytitle><manvolnum>1</manvolnum></citerefentry>. It
+    exposes the detected cloud vendor and the well-known <literal>hostname</literal>,
+    <literal>region</literal>, <literal>zone</literal>, <literal>ipv4-public</literal> and
+    <literal>ipv6-public</literal> fields as metrics in the <literal>io.systemd.InstanceMetadata.</literal>
+    namespace. The data is acquired on demand from
+    <citerefentry><refentrytitle>systemd-imdsd@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+    benefiting from its local cache.</para>
   </refsect1>
 
   <refsect1>
       <member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
       <member><citerefentry><refentrytitle>systemd-imdsd@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
       <member><citerefentry><refentrytitle>systemd-imds-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
+      <member><citerefentry><refentrytitle>systemd-report</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
       <member><citerefentry><refentrytitle>systemd.system-credentials</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
     </simplelist></para>
   </refsect1>
index 854877d40ed28f6b1e2ac3e331249af65a44276c..3f22ca303105d59fd7d539d43bee2c3a8c0ce483 100644 (file)
@@ -189,6 +189,11 @@ static int run(const char *dest, const char *dest_early, const char *dest_late)
                         return log_error_errno(r, "Failed to hook in systemd-imds-import.service: %m");
         }
 
+        /* Enable IMDS metrics in case IMDS is supported */
+        r = generator_add_symlink(dest_early, SPECIAL_SOCKETS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/systemd-imds-metrics.socket");
+        if (r < 0)
+                return log_error_errno(r, "Failed to hook in systemd-imds-metrics.socket: %m");
+
         return 0;
 }
 
diff --git a/src/imds/imds-tool-metrics.c b/src/imds/imds-tool-metrics.c
new file mode 100644 (file)
index 0000000..27eabe9
--- /dev/null
@@ -0,0 +1,164 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-json.h"
+#include "sd-varlink.h"
+
+#include "imds-tool.h"
+#include "imds-tool-metrics.h"
+#include "imds-util.h"
+#include "log.h"
+#include "metrics.h"
+#include "string-util.h"
+#include "varlink-io.systemd.Metrics.h"
+#include "varlink-util.h"
+
+#define METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "io.systemd.InstanceMetadata."
+
+static int metric_vendor_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
+        sd_varlink *imds = userdata; /* The live systemd-imdsd connection, or NULL if unavailable */
+        _cleanup_free_ char *vendor = NULL;
+        int r;
+
+        assert(mf && mf->name);
+        assert(vl);
+
+        if (!imds)
+                return 0;
+
+        r = acquire_imds_vendor(imds, &vendor);
+        if (r <= 0)
+                return 0; /* On error or absence simply omit the metric */
+
+        return metric_build_send_string(mf, vl, /* object= */ NULL, vendor, /* fields= */ NULL);
+}
+
+static int metric_well_known_build_json(
+                const MetricFamily *mf,
+                sd_varlink *vl,
+                void *userdata,
+                ImdsWellKnown wk) {
+
+        sd_varlink *imds = userdata;
+        _cleanup_free_ char *value = NULL;
+        int r;
+
+        assert(mf && mf->name);
+        assert(vl);
+
+        if (!imds)
+                return 0;
+
+        r = acquire_imds_key_as_string(imds, wk, /* key= */ NULL, &value);
+        if (r <= 0 || isempty(value))
+                return 0; /* Field not supported/set, or error: omit the metric */
+
+        return metric_build_send_string(mf, vl, /* object= */ NULL, value, /* fields= */ NULL);
+}
+
+static int metric_hostname_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
+        return metric_well_known_build_json(mf, vl, userdata, IMDS_HOSTNAME);
+}
+
+static int metric_ipv4_public_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
+        return metric_well_known_build_json(mf, vl, userdata, IMDS_IPV4_PUBLIC);
+}
+
+static int metric_ipv6_public_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
+        return metric_well_known_build_json(mf, vl, userdata, IMDS_IPV6_PUBLIC);
+}
+
+static int metric_region_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
+        return metric_well_known_build_json(mf, vl, userdata, IMDS_REGION);
+}
+
+static int metric_zone_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
+        return metric_well_known_build_json(mf, vl, userdata, IMDS_ZONE);
+}
+
+/* Keep metrics ordered alphabetically */
+static const MetricFamily imds_metric_family_table[] = {
+        {
+                .name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "Hostname",
+                .description = "Instance hostname reported by IMDS",
+                .type = METRIC_FAMILY_TYPE_STRING,
+                .generate = metric_hostname_build_json,
+        },
+        {
+                .name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "IPv4Public",
+                .description = "Public IPv4 address reported by IMDS",
+                .type = METRIC_FAMILY_TYPE_STRING,
+                .generate = metric_ipv4_public_build_json,
+        },
+        {
+                .name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "IPv6Public",
+                .description = "Public IPv6 address reported by IMDS",
+                .type = METRIC_FAMILY_TYPE_STRING,
+                .generate = metric_ipv6_public_build_json,
+        },
+        {
+                .name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "Region",
+                .description = "Cloud region reported by IMDS",
+                .type = METRIC_FAMILY_TYPE_STRING,
+                .generate = metric_region_build_json,
+        },
+        {
+                .name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "Vendor",
+                .description = "Detected cloud vendor",
+                .type = METRIC_FAMILY_TYPE_STRING,
+                .generate = metric_vendor_build_json,
+        },
+        {
+                .name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "Zone",
+                .description = "Cloud availability zone reported by IMDS",
+                .type = METRIC_FAMILY_TYPE_STRING,
+                .generate = metric_zone_build_json,
+        },
+        {}
+};
+
+static int vl_method_metrics_describe(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
+        return metrics_method_describe(imds_metric_family_table, link, parameters, flags, userdata);
+}
+
+static int vl_method_metrics_list(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
+        int r;
+
+        /* Acquire a single connection to systemd-imdsd, shared by all metric generators (so they benefit
+         * from the daemon's token/data cache). If the daemon is unreachable we still serve an empty list
+         * rather than failing the whole request. */
+        _cleanup_(sd_varlink_unrefp) sd_varlink *imds = NULL;
+        r = connect_imdsd(&imds);
+        if (r < 0)
+                log_debug_errno(r, "Failed to connect to systemd-imdsd, serving empty metrics list: %m");
+
+        return metrics_method_list(imds_metric_family_table, link, parameters, flags, imds);
+}
+
+int imds_metrics_run(void) {
+        int r;
+
+        /* Invocation as a Varlink metrics provider (io.systemd.Metrics), typically socket-activated via
+         * /run/systemd/report/io.systemd.InstanceMetadata. */
+
+        _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *server = NULL;
+        r = varlink_server_new(&server, /* flags= */ 0, /* userdata= */ NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate Varlink server: %m");
+
+        r = sd_varlink_server_add_interface(server, &vl_interface_io_systemd_Metrics);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add Varlink interface: %m");
+
+        r = sd_varlink_server_bind_method_many(
+                        server,
+                        "io.systemd.Metrics.List",     vl_method_metrics_list,
+                        "io.systemd.Metrics.Describe", vl_method_metrics_describe);
+        if (r < 0)
+                return log_error_errno(r, "Failed to bind Varlink methods: %m");
+
+        r = sd_varlink_server_loop_auto(server);
+        if (r < 0)
+                return log_error_errno(r, "Failed to run Varlink event loop: %m");
+
+        return 0;
+}
diff --git a/src/imds/imds-tool-metrics.h b/src/imds/imds-tool-metrics.h
new file mode 100644 (file)
index 0000000..db0e552
--- /dev/null
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/* Runs systemd-imds as an io.systemd.Metrics provider (socket-activated). */
+int imds_metrics_run(void);
index 6b58c61d7e89f16cf83d7e270514f06dc70d5b43..bc0a6b1103b0abf471d1cbf223523de98bb3033e 100644 (file)
@@ -18,6 +18,8 @@
 #include "format-util.h"
 #include "fs-util.h"
 #include "hexdecoct.h"
+#include "imds-tool.h"
+#include "imds-tool-metrics.h"
 #include "imds-util.h"
 #include "in-addr-util.h"
 #include "io-util.h"
@@ -232,7 +234,7 @@ static int acquire_imds_key(
         return 1;
 }
 
-static int acquire_imds_key_as_string(
+int acquire_imds_key_as_string(
                 sd_varlink *link,
                 ImdsWellKnown wk,
                 const char *key,
@@ -292,14 +294,11 @@ static int acquire_imds_key_as_ip_address(
         return 1;
 }
 
-static int action_summary(sd_varlink *link) {
+int acquire_imds_vendor(sd_varlink *link, char **ret) {
         int r;
 
         assert(link);
-
-        _cleanup_(table_unrefp) Table *table = table_new_vertical();
-        if (!table)
-                return log_oom();
+        assert(ret);
 
         const char *error_id = NULL;
         sd_json_variant *reply = NULL;
@@ -311,17 +310,48 @@ static int action_summary(sd_varlink *link) {
                         &error_id);
         if (r < 0)
                 return log_error_errno(r, "Failed to issue io.systemd.InstanceMetadata.GetVendorInfo(): %m");
-        if (error_id)
+        if (error_id) {
+                if (streq(error_id, "io.systemd.InstanceMetadata.NotSupported")) {
+                        *ret = NULL;
+                        return 0;
+                }
+
                 return log_error_errno(sd_varlink_error_to_errno(error_id, reply), "Failed to issue io.systemd.InstanceMetadata.GetVendorInfo(): %s", error_id);
+        }
 
         const char *vendor = NULL;
         static const sd_json_dispatch_field dispatch_table[] = {
-                { "vendor",    SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 },
+                { "vendor", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 },
                 {}
         };
         r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG, &vendor);
         if (r < 0)
                 return r;
+        if (isempty(vendor)) {
+                *ret = NULL;
+                return 0;
+        }
+
+        if (strdup_to_full(ret, vendor) < 0)
+                return log_oom();
+
+        return 1;
+}
+
+static int action_summary(sd_varlink *link) {
+        int r;
+
+        assert(link);
+
+        _cleanup_(table_unrefp) Table *table = table_new_vertical();
+        if (!table)
+                return log_oom();
+
+        _cleanup_free_ char *vendor = NULL;
+        r = acquire_imds_vendor(link, &vendor);
+        if (r < 0)
+                return r;
+
         if (vendor) {
                 r = table_add_many(table,
                                    TABLE_FIELD, "Vendor",
@@ -820,14 +850,10 @@ static int action_import(sd_varlink *link) {
         return RET_GATHER(ret, import_credentials(data.iov_base));
 }
 
-static int run(int argc, char* argv[]) {
+int connect_imdsd(sd_varlink **ret) {
         int r;
 
-        log_setup();
-
-        r = parse_argv(argc, argv);
-        if (r <= 0)
-                return r;
+        assert(ret);
 
         _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL;
         r = sd_varlink_connect_address(&link, "/run/systemd/io.systemd.InstanceMetadata");
@@ -850,6 +876,33 @@ static int run(int argc, char* argv[]) {
                         return log_error_errno(r, "Failed to connect to imdsd service: %m");
         }
 
+        *ret = TAKE_PTR(link);
+        return 0;
+}
+
+static int run(int argc, char* argv[]) {
+        int r;
+
+        log_setup();
+
+        /* When invoked as a Varlink service (socket activation) we act as an io.systemd.Metrics provider
+         * and run a server loop. The metric generators connect to systemd-imdsd on demand, so we never open
+         * a one-shot connection here. */
+        r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
+        if (r < 0)
+                return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
+        if (r > 0)
+                return imds_metrics_run();
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r;
+
+        _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL;
+        r = connect_imdsd(&link);
+        if (r < 0)
+                return r;
+
         switch (arg_action) {
 
         case ACTION_SUMMARY:
diff --git a/src/imds/imds-tool.h b/src/imds/imds-tool.h
new file mode 100644 (file)
index 0000000..bfe2601
--- /dev/null
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "imds-util.h"
+#include "shared-forward.h"
+
+/* Helpers shared between imds-tool.c and imds-tool-metrics.c for talking to systemd-imdsd. */
+
+int connect_imdsd(sd_varlink **ret);
+int acquire_imds_key_as_string(sd_varlink *link, ImdsWellKnown wk, const char *key, char **ret);
+int acquire_imds_vendor(sd_varlink *link, char **ret);
index 9295c8cf620c97bba6a8da92d6d23514e4ab6342..a23735d100219fd049504a2652f0675c4982420b 100644 (file)
@@ -14,7 +14,9 @@ executables += [
         libexec_template + {
                 'name' : 'systemd-imds',
                 'public' : true,
-                'sources' : files('imds-tool.c'),
+                'sources' : files(
+                        'imds-tool.c',
+                        'imds-tool-metrics.c'),
                 'objects' : ['systemd-imdsd'],
         },
         generator_template + {
index 47c48bcabcc101cd89ab87728d72bea7f887bd86..34051d63c32aa2f0e110f6bbd5f18f5f3a8bfdbf 100644 (file)
@@ -413,6 +413,14 @@ units = [
           'file' : 'systemd-imds-import.service.in',
           'conditions' : ['ENABLE_IMDS'],
         },
+        {
+          'file' : 'systemd-imds-metrics.socket',
+          'conditions' : ['ENABLE_IMDS'],
+        },
+        {
+          'file' : 'systemd-imds-metrics@.service.in',
+          'conditions' : ['ENABLE_IMDS'],
+        },
         {
           'file' : 'systemd-importd.service.in',
           'conditions' : ['ENABLE_IMPORTD'],
diff --git a/units/systemd-imds-metrics.socket b/units/systemd-imds-metrics.socket
new file mode 100644 (file)
index 0000000..f71a31e
--- /dev/null
@@ -0,0 +1,22 @@
+#  SPDX-License-Identifier: LGPL-2.1-or-later
+#
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Cloud Instance Metadata Metrics
+Documentation=man:systemd-imds(1)
+DefaultDependencies=no
+Before=sockets.target
+
+[Socket]
+ListenStream=/run/systemd/report/io.systemd.InstanceMetadata
+FileDescriptorName=varlink
+SocketMode=0666
+Accept=yes
+MaxConnectionsPerSource=16
+RemoveOnStop=yes
diff --git a/units/systemd-imds-metrics@.service.in b/units/systemd-imds-metrics@.service.in
new file mode 100644 (file)
index 0000000..f8da319
--- /dev/null
@@ -0,0 +1,25 @@
+#  SPDX-License-Identifier: LGPL-2.1-or-later
+#
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Cloud Instance Metadata Metrics
+Documentation=man:systemd-imds(1)
+DefaultDependencies=no
+Conflicts=shutdown.target initrd-switch-root.target
+Before=shutdown.target initrd-switch-root.target
+After=systemd-imdsd.socket
+
+[Service]
+# Runs systemd-imds as an io.systemd.Metrics provider. It connects to
+# systemd-imdsd to acquire the actual data. Runs as root so the daemon's Polkit
+# check on io.systemd.InstanceMetadata.Get() passes.
+ExecStart={{LIBEXECDIR}}/systemd-imds
+LockPersonality=yes
+MemoryDenyWriteExecute=yes
+NoNewPrivileges=yes