[pve-devel] [PATCH installer] fetch-answer: add `$fetchinfo` meta-field to HTTP POST payload

Christoph Heiss c.heiss at proxmox.com
Wed Nov 13 10:06:36 CET 2024


This adds a metadata-field `$fetchinfo` containing a single key
`version` (for now) to the POST payload json, indicating which schema
version (and thus structure) this document uses.

The version field follows the format "<major>.<minor>" and applies
semantic versioning meaning for both the major and minor number. A patch
version is left out here, as it doesn't make much sense in this context.

Works in the same manner as the post-hook does it [0]. Useful to have it
for this too, as we might change/expand this structure too in the
future.

In the resulting JSON, this will look like this:
{
    "$fetchinfo": {
	"version": "1.0"
    },
    "product": ..,
    ..
}

[0] https://lore.proxmox.com/pve-devel/20241112145405.929194-1-c.heiss@proxmox.com/

Signed-off-by: Christoph Heiss <c.heiss at proxmox.com>
---
 proxmox-auto-installer/src/sysinfo.rs         |  5 --
 proxmox-fetch-answer/Cargo.toml               |  2 +
 .../src/fetch_plugins/http.rs                 | 65 ++++++++++++++++++-
 3 files changed, 66 insertions(+), 6 deletions(-)

diff --git a/proxmox-auto-installer/src/sysinfo.rs b/proxmox-auto-installer/src/sysinfo.rs
index 0a6aaf2..a45ae59 100644
--- a/proxmox-auto-installer/src/sysinfo.rs
+++ b/proxmox-auto-installer/src/sysinfo.rs
@@ -41,11 +41,6 @@ impl SysInfo {
         let info = Self::get()?;
         Ok(serde_json::to_string_pretty(&info)?)
     }
-
-    pub fn as_json() -> Result<String> {
-        let info = Self::get()?;
-        Ok(serde_json::to_string(&info)?)
-    }
 }
 
 #[derive(Debug, Serialize)]
diff --git a/proxmox-fetch-answer/Cargo.toml b/proxmox-fetch-answer/Cargo.toml
index 50f3da3..23bd094 100644
--- a/proxmox-fetch-answer/Cargo.toml
+++ b/proxmox-fetch-answer/Cargo.toml
@@ -15,4 +15,6 @@ anyhow.workspace = true
 log.workspace = true
 proxmox-auto-installer.workspace = true
 proxmox-installer-common = { workspace = true, features = ["http"] }
+serde = { workspace = true, features = ["derive"] }
+serde_json.workspace = true
 toml.workspace = true
diff --git a/proxmox-fetch-answer/src/fetch_plugins/http.rs b/proxmox-fetch-answer/src/fetch_plugins/http.rs
index 4317430..fb9ffc8 100644
--- a/proxmox-fetch-answer/src/fetch_plugins/http.rs
+++ b/proxmox-fetch-answer/src/fetch_plugins/http.rs
@@ -1,5 +1,6 @@
 use anyhow::{bail, Result};
 use log::info;
+use serde::Serialize;
 use std::{
     fs::{self, read_to_string},
     process::Command,
@@ -28,6 +29,67 @@ static DHCP_URL_OPTION: &str = "proxmox-auto-installer-manifest-url";
 static DHCP_CERT_FP_OPTION: &str = "proxmox-auto-installer-cert-fingerprint";
 static DHCP_LEASE_FILE: &str = "/var/lib/dhcp/dhclient.leases";
 
+/// Metadata of the HTTP POST payload, such as schema version of the document.
+#[derive(Serialize)]
+#[serde(rename_all = "kebab-case")]
+struct HttpFetchInfoMeta {
+    /// major.minor version describing the schema version of this document, in a semanticy-version
+    /// way.
+    ///
+    /// major: Incremented for incompatible/breaking API changes, e.g. removing an existing
+    /// field.
+    /// minor: Incremented when adding functionality in a backwards-compatible matter, e.g.
+    /// adding a new field.
+    version: String,
+}
+
+impl HttpFetchInfoMeta {
+    const SCHEMA_VERSION: &str = "1.0";
+}
+
+impl Default for HttpFetchInfoMeta {
+    fn default() -> Self {
+        Self {
+            version: Self::SCHEMA_VERSION.to_owned(),
+        }
+    }
+}
+
+/// All data sent as request payload with the answerfile fetch POST request.
+///
+/// NOTE: The format is versioned through `fetch_meta.version` (`$fetchinfo.version` in the
+/// resulting JSON), ensure you update it when this struct or any of its members gets modified.
+#[derive(Serialize)]
+#[serde(rename_all = "kebab-case")]
+struct HttpFetchPayload {
+    /// Metadata for the answerfile fetch payload
+    // This field is prefixed by `$` on purpose, to indicate that it is document metadata and not
+    // part of the actual content itself. (E.g. JSON Schema uses a similar naming scheme)
+    #[serde(rename = "$fetchinfo")]
+    fetch_meta: HttpFetchInfoMeta,
+    /// Information about the running system, flattened into this structure directly.
+    #[serde(flatten)]
+    sysinfo: SysInfo,
+}
+
+impl HttpFetchPayload {
+    /// Retrieves the required information from the system and constructs the
+    /// full payload including meta data.
+    fn get() -> Result<Self> {
+        Ok(Self {
+            fetch_meta: HttpFetchInfoMeta::default(),
+            sysinfo: SysInfo::get()?,
+        })
+    }
+
+    /// Retrieves the required information from the system and constructs the
+    /// full payload including meta data, serialized as JSON.
+    pub fn as_json() -> Result<String> {
+        let info = Self::get()?;
+        Ok(serde_json::to_string(&info)?)
+    }
+}
+
 pub struct FetchFromHTTP;
 
 impl FetchFromHTTP {
@@ -65,7 +127,8 @@ impl FetchFromHTTP {
         }
 
         info!("Gathering system information.");
-        let payload = SysInfo::as_json()?;
+        let payload = HttpFetchPayload::as_json()?;
+
         info!("Sending POST request to '{answer_url}'.");
         let answer =
             proxmox_installer_common::http::post(&answer_url, fingerprint.as_deref(), payload)?;
-- 
2.47.0





More information about the pve-devel mailing list