public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Lukas Wagner <l.wagner@proxmox.com>
To: pdm-devel@lists.proxmox.com
Subject: [RFC datacenter-manager 4/6] remote updates: use PdmApplication object to derive paths, permissions and client factory
Date: Thu, 29 Jan 2026 14:44:16 +0100	[thread overview]
Message-ID: <20260129134418.307552-6-l.wagner@proxmox.com> (raw)
In-Reply-To: <20260129134418.307552-1-l.wagner@proxmox.com>

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
 server/src/api/pve/mod.rs                     | 11 ++-
 server/src/api/remote_updates.rs              | 54 +++++++----
 server/src/bin/proxmox-datacenter-api/main.rs |  2 +-
 .../tasks/remote_updates.rs                   | 22 +++--
 server/src/remote_updates.rs                  | 90 ++++++++++++-------
 5 files changed, 124 insertions(+), 55 deletions(-)

diff --git a/server/src/api/pve/mod.rs b/server/src/api/pve/mod.rs
index 97dc3970..21e247f1 100644
--- a/server/src/api/pve/mod.rs
+++ b/server/src/api/pve/mod.rs
@@ -32,8 +32,8 @@ use super::resources::{map_pve_lxc, map_pve_node, map_pve_qemu, map_pve_storage}
 
 use crate::connection::PveClient;
 use crate::connection::{self, probe_tls_connection};
-use crate::remote_tasks;
 use crate::remote_updates::get_available_updates_for_remote;
+use crate::{context, remote_tasks};
 
 mod firewall;
 mod lxc;
@@ -546,8 +546,13 @@ pub async fn get_options(remote: String) -> Result<serde_json::Value, Error> {
     },
 )]
 /// Return the cached update information about a remote.
-pub fn get_updates(remote: String) -> Result<RemoteUpdateSummary, Error> {
-    let update_summary = get_available_updates_for_remote(&remote)?;
+pub fn get_updates(
+    remote: String,
+    rpcenv: &mut dyn RpcEnvironment,
+) -> Result<RemoteUpdateSummary, Error> {
+    let app = context::get_application(rpcenv);
+
+    let update_summary = get_available_updates_for_remote(&app, &remote)?;
 
     Ok(update_summary)
 }
diff --git a/server/src/api/remote_updates.rs b/server/src/api/remote_updates.rs
index 4b2b41cd..a8ea2182 100644
--- a/server/src/api/remote_updates.rs
+++ b/server/src/api/remote_updates.rs
@@ -17,7 +17,8 @@ use proxmox_router::{
 use proxmox_schema::api;
 use proxmox_sortable_macro::sortable;
 
-use crate::{connection, remote_updates};
+use crate::context;
+use crate::remote_updates;
 
 use super::remotes::get_remote;
 
@@ -43,6 +44,8 @@ const SUBDIRS: SubdirMap = &sorted!([
 )]
 /// Return available update summary for managed remote nodes.
 pub fn update_summary(rpcenv: &mut dyn RpcEnvironment) -> Result<UpdateSummary, Error> {
+    let app = context::get_application(rpcenv);
+
     let auth_id = rpcenv.get_auth_id().unwrap().parse()?;
     let user_info = CachedUserInfo::new()?;
 
@@ -50,7 +53,7 @@ pub fn update_summary(rpcenv: &mut dyn RpcEnvironment) -> Result<UpdateSummary,
         http_bail!(FORBIDDEN, "user has no access to resources");
     }
 
-    let mut update_summary = remote_updates::get_available_updates_summary()?;
+    let mut update_summary = remote_updates::get_available_updates_summary(app.as_ref())?;
 
     update_summary.remotes.retain(|remote_name, _| {
         user_info
@@ -75,7 +78,9 @@ pub fn update_summary(rpcenv: &mut dyn RpcEnvironment) -> Result<UpdateSummary,
 )]
 /// Refresh the update summary of all remotes.
 pub fn refresh_remote_update_summaries(rpcenv: &mut dyn RpcEnvironment) -> Result<UPID, Error> {
-    let (config, _digest) = pdm_config::remotes::config()?;
+    let app = context::get_application(rpcenv);
+
+    let (config, _digest) = app.remote_config().config()?;
 
     let auth_id = rpcenv.get_auth_id().unwrap().parse()?;
     let user_info = CachedUserInfo::new()?;
@@ -105,9 +110,10 @@ pub fn refresh_remote_update_summaries(rpcenv: &mut dyn RpcEnvironment) -> Resul
         auth_id.to_string(),
         true,
         |_worker| async {
+            let app = app;
             // TODO: Add more verbose logging per remote/node, so we can actually see something
             // interesting in the task log.
-            remote_updates::refresh_update_summary_cache(remotes).await?;
+            remote_updates::refresh_update_summary_cache(app.as_ref(), remotes).await?;
             Ok(())
         },
     )?;
@@ -138,11 +144,17 @@ pub fn refresh_remote_update_summaries(rpcenv: &mut dyn RpcEnvironment) -> Resul
     },
 )]
 /// List available APT updates for a remote PVE node.
-async fn apt_update_available(remote: String, node: String) -> Result<Vec<APTUpdateInfo>, Error> {
-    let (config, _digest) = pdm_config::remotes::config()?;
+async fn apt_update_available(
+    remote: String,
+    node: String,
+    rpcenv: &mut dyn RpcEnvironment,
+) -> Result<Vec<APTUpdateInfo>, Error> {
+    let app = context::get_application(rpcenv);
+    let (config, _digest) = app.remote_config().config()?;
     let remote = get_remote(&config, &remote)?;
 
-    let updates = remote_updates::list_available_updates(remote.clone(), &node).await?;
+    let updates =
+        remote_updates::list_available_updates(app.as_ref(), remote.clone(), &node).await?;
 
     Ok(updates)
 }
@@ -164,11 +176,17 @@ async fn apt_update_available(remote: String, node: String) -> Result<Vec<APTUpd
     returns: { type: RemoteUpid }
 )]
 /// Update the APT database of a remote PVE node.
-pub async fn apt_update_database(remote: String, node: String) -> Result<RemoteUpid, Error> {
-    let (config, _digest) = pdm_config::remotes::config()?;
+pub async fn apt_update_database(
+    remote: String,
+    node: String,
+    rpcenv: &mut dyn RpcEnvironment,
+) -> Result<RemoteUpid, Error> {
+    let app = context::get_application(rpcenv);
+
+    let (config, _digest) = app.remote_config().config()?;
     let remote = get_remote(&config, &remote)?;
 
-    let upid = remote_updates::update_apt_database(remote, &node).await?;
+    let upid = remote_updates::update_apt_database(&app, remote, &node).await?;
 
     Ok(upid)
 }
@@ -201,11 +219,14 @@ async fn apt_get_changelog(
     remote: String,
     node: String,
     options: APTGetChangelogOptions,
+    rpcenv: &mut dyn RpcEnvironment,
 ) -> Result<String, Error> {
-    let (config, _digest) = pdm_config::remotes::config()?;
+    let app = context::get_application(rpcenv);
+
+    let (config, _digest) = app.remote_config().config()?;
     let remote = get_remote(&config, &remote)?;
 
-    remote_updates::get_changelog(remote, &node, options.name).await
+    remote_updates::get_changelog(&app, remote, &node, options.name).await
 }
 
 #[api(
@@ -227,17 +248,20 @@ async fn apt_get_changelog(
 async fn get_apt_repositories(
     remote: String,
     node: String,
+    rpcenv: &mut dyn RpcEnvironment,
 ) -> Result<APTRepositoriesResult, Error> {
-    let (config, _digest) = pdm_config::remotes::config()?;
+    let app = context::get_application(rpcenv);
+    let (config, _digest) = app.remote_config().config()?;
+
     let remote = get_remote(&config, &remote)?;
 
     Ok(match remote.ty {
         RemoteType::Pve => {
-            let client = connection::make_pve_client(remote)?;
+            let client = app.client_factory().make_pve_client(remote)?;
             client.get_apt_repositories(&node).await?
         }
         RemoteType::Pbs => {
-            let client = connection::make_pbs_client(remote)?;
+            let client = app.client_factory().make_pbs_client(remote)?;
             client.get_apt_repositories().await?
         }
     })
diff --git a/server/src/bin/proxmox-datacenter-api/main.rs b/server/src/bin/proxmox-datacenter-api/main.rs
index e674e763..0bf8d128 100644
--- a/server/src/bin/proxmox-datacenter-api/main.rs
+++ b/server/src/bin/proxmox-datacenter-api/main.rs
@@ -335,7 +335,7 @@ async fn run(debug: bool) -> Result<(), Error> {
     tasks::remote_node_mapping::start_task();
     resource_cache::start_task();
     tasks::remote_tasks::start_task()?;
-    tasks::remote_updates::start_task()?;
+    tasks::remote_updates::start_task(app)?;
 
     server.await?;
     log::info!("server shutting down, waiting for active workers to complete");
diff --git a/server/src/bin/proxmox-datacenter-api/tasks/remote_updates.rs b/server/src/bin/proxmox-datacenter-api/tasks/remote_updates.rs
index bd9e178d..a255bc12 100644
--- a/server/src/bin/proxmox-datacenter-api/tasks/remote_updates.rs
+++ b/server/src/bin/proxmox-datacenter-api/tasks/remote_updates.rs
@@ -1,13 +1,16 @@
+use std::sync::Arc;
+
 use anyhow::Error;
 
+use server::context::PdmApplication;
 use server::{remote_updates, task_utils};
 
 const REFRESH_TIME: u64 = 6 * 3600;
 
 /// Start the remote task fetching task
-pub fn start_task() -> Result<(), Error> {
+pub fn start_task(app: Arc<PdmApplication>) -> Result<(), Error> {
     tokio::spawn(async move {
-        let task_scheduler = std::pin::pin!(RemoteUpdateRefreshTask {}.run());
+        let task_scheduler = std::pin::pin!(RemoteUpdateRefreshTask::new(app).run());
         let abort_future = std::pin::pin!(proxmox_daemon::shutdown_future());
         futures::future::select(task_scheduler, abort_future).await;
     });
@@ -15,9 +18,15 @@ pub fn start_task() -> Result<(), Error> {
     Ok(())
 }
 
-struct RemoteUpdateRefreshTask {}
+struct RemoteUpdateRefreshTask {
+    app: Arc<PdmApplication>,
+}
 
 impl RemoteUpdateRefreshTask {
+    fn new(app: Arc<PdmApplication>) -> Self {
+        Self { app }
+    }
+
     async fn run(self) {
         loop {
             self.refresh().await;
@@ -33,8 +42,11 @@ impl RemoteUpdateRefreshTask {
 
     async fn do_refresh(&self) -> Result<(), Error> {
         let (config, _digest) = tokio::task::spawn_blocking(pdm_config::remotes::config).await??;
-        remote_updates::refresh_update_summary_cache(config.into_iter().map(|(_, r)| r).collect())
-            .await
+        remote_updates::refresh_update_summary_cache(
+            self.app.as_ref(),
+            config.into_iter().map(|(_, r)| r).collect(),
+        )
+        .await
     }
 
     async fn wait_for_refresh(&self) {
diff --git a/server/src/remote_updates.rs b/server/src/remote_updates.rs
index e772eef5..c169eac3 100644
--- a/server/src/remote_updates.rs
+++ b/server/src/remote_updates.rs
@@ -1,5 +1,7 @@
 use std::fs::File;
 use std::io::ErrorKind;
+use std::path::{Path, PathBuf};
+use std::sync::Arc;
 
 use anyhow::{bail, Error};
 use serde::{Deserialize, Serialize};
@@ -12,12 +14,12 @@ use pdm_api_types::remote_updates::{
 };
 use pdm_api_types::remotes::{Remote, RemoteType};
 use pdm_api_types::RemoteUpid;
-use pdm_buildcfg::PDM_CACHE_DIR_M;
 
-use crate::connection;
+use crate::connection::ClientFactory;
+use crate::context::PdmApplication;
 use crate::parallel_fetcher::{NodeResults, ParallelFetcher};
 
-pub const UPDATE_CACHE: &str = concat!(PDM_CACHE_DIR_M!(), "/remote-updates.json");
+const CACHE_FILENAME: &str = "remote-updates.json";
 
 #[derive(Clone, Default, Debug, Deserialize, Serialize)]
 #[serde(rename_all = "kebab-case")]
@@ -43,12 +45,14 @@ impl From<NodeUpdateInfo> for NodeUpdateSummary {
 
 /// Return a list of available updates for a given remote node.
 pub async fn list_available_updates(
+    app: &PdmApplication,
     remote: Remote,
     node: &str,
 ) -> Result<Vec<APTUpdateInfo>, Error> {
-    let updates = fetch_available_updates((), remote.clone(), node.to_string()).await?;
+    let updates =
+        fetch_available_updates(app.client_factory(), remote.clone(), node.to_string()).await?;
 
-    update_cached_summary_for_node(remote, node.into(), updates.clone().into()).await?;
+    update_cached_summary_for_node(app, remote, node.into(), updates.clone().into()).await?;
 
     Ok(updates.updates)
 }
@@ -56,10 +60,14 @@ pub async fn list_available_updates(
 /// Trigger `apt update` on a remote node.
 ///
 /// The function returns a `[RemoteUpid]` for the started update task.
-pub async fn update_apt_database(remote: &Remote, node: &str) -> Result<RemoteUpid, Error> {
+pub async fn update_apt_database(
+    app: &PdmApplication,
+    remote: &Remote,
+    node: &str,
+) -> Result<RemoteUpid, Error> {
     match remote.ty {
         RemoteType::Pve => {
-            let client = connection::make_pve_client(remote)?;
+            let client = app.client_factory().make_pve_client(remote)?;
 
             let params = pve_api_types::AptUpdateParams {
                 notify: Some(false),
@@ -70,7 +78,7 @@ pub async fn update_apt_database(remote: &Remote, node: &str) -> Result<RemoteUp
             crate::api::pve::new_remote_upid(remote.id.clone(), upid).await
         }
         RemoteType::Pbs => {
-            let client = connection::make_pbs_client(remote)?;
+            let client = app.client_factory().make_pbs_client(remote)?;
 
             let params = crate::pbs_client::AptUpdateParams {
                 notify: Some(false),
@@ -84,10 +92,15 @@ pub async fn update_apt_database(remote: &Remote, node: &str) -> Result<RemoteUp
 }
 
 /// Get the changelog for a given package.
-pub async fn get_changelog(remote: &Remote, node: &str, package: String) -> Result<String, Error> {
+pub async fn get_changelog(
+    app: &PdmApplication,
+    remote: &Remote,
+    node: &str,
+    package: String,
+) -> Result<String, Error> {
     match remote.ty {
         RemoteType::Pve => {
-            let client = connection::make_pve_client(remote)?;
+            let client = app.client_factory().make_pve_client(remote)?;
 
             client
                 .get_package_changelog(node, package, None)
@@ -95,7 +108,7 @@ pub async fn get_changelog(remote: &Remote, node: &str, package: String) -> Resu
                 .map_err(Into::into)
         }
         RemoteType::Pbs => {
-            let client = connection::make_pbs_client(remote)?;
+            let client = app.client_factory().make_pbs_client(remote)?;
 
             client
                 .get_package_changelog(package, None)
@@ -106,10 +119,10 @@ pub async fn get_changelog(remote: &Remote, node: &str, package: String) -> Resu
 }
 
 /// Get update summary for all managed remotes.
-pub fn get_available_updates_summary() -> Result<UpdateSummary, Error> {
-    let (config, _digest) = pdm_config::remotes::config()?;
+pub fn get_available_updates_summary(app: &PdmApplication) -> Result<UpdateSummary, Error> {
+    let (config, _digest) = app.remote_config().config()?;
 
-    let cache_content = get_cached_summary_or_default()?;
+    let cache_content = get_cached_summary_or_default(get_cache_path(app))?;
 
     let mut summary = UpdateSummary::default();
 
@@ -137,11 +150,14 @@ pub fn get_available_updates_summary() -> Result<UpdateSummary, Error> {
 }
 
 /// Return cached update information from specific remote
-pub fn get_available_updates_for_remote(remote: &str) -> Result<RemoteUpdateSummary, Error> {
-    let (config, _digest) = pdm_config::remotes::config()?;
+pub fn get_available_updates_for_remote(
+    app: &PdmApplication,
+    remote: &str,
+) -> Result<RemoteUpdateSummary, Error> {
+    let (config, _digest) = app.remote_config().config()?;
 
     if let Some(remote) = config.get(remote) {
-        let cache_content = get_cached_summary_or_default()?;
+        let cache_content = get_cached_summary_or_default(get_cache_path(app))?;
         Ok(cache_content
             .remotes
             .get(&remote.id)
@@ -156,8 +172,12 @@ pub fn get_available_updates_for_remote(remote: &str) -> Result<RemoteUpdateSumm
     }
 }
 
-fn get_cached_summary_or_default() -> Result<UpdateSummary, Error> {
-    match File::open(UPDATE_CACHE) {
+fn get_cache_path(app: &PdmApplication) -> PathBuf {
+    app.cache_path().join(CACHE_FILENAME)
+}
+
+fn get_cached_summary_or_default<P: AsRef<Path>>(cache_path: P) -> Result<UpdateSummary, Error> {
+    match File::open(cache_path.as_ref()) {
         Ok(file) => {
             let content = match serde_json::from_reader(file) {
                 Ok(cache_content) => cache_content,
@@ -175,11 +195,12 @@ fn get_cached_summary_or_default() -> Result<UpdateSummary, Error> {
 }
 
 async fn update_cached_summary_for_node(
+    app: &PdmApplication,
     remote: Remote,
     node: String,
     node_data: NodeUpdateSummary,
 ) -> Result<(), Error> {
-    let mut file = File::open(UPDATE_CACHE)?;
+    let mut file = File::open(get_cache_path(app))?;
     let mut cache_content: UpdateSummary = serde_json::from_reader(&mut file)?;
     let remote_entry =
         cache_content
@@ -193,11 +214,10 @@ async fn update_cached_summary_for_node(
 
     remote_entry.nodes.insert(node, node_data);
 
-    let options = proxmox_product_config::default_create_options();
     proxmox_sys::fs::replace_file(
-        UPDATE_CACHE,
+        get_cache_path(app),
         &serde_json::to_vec(&cache_content)?,
-        options,
+        app.default_create_options(),
         true,
     )?;
 
@@ -205,14 +225,18 @@ async fn update_cached_summary_for_node(
 }
 
 /// Refresh the remote update cache.
-pub async fn refresh_update_summary_cache(remotes: Vec<Remote>) -> Result<(), Error> {
-    let fetcher = ParallelFetcher::new(());
+pub async fn refresh_update_summary_cache(
+    app: &PdmApplication,
+    remotes: Vec<Remote>,
+) -> Result<(), Error> {
+    let fetcher =
+        ParallelFetcher::new_with_client_factory(app.client_factory(), app.client_factory());
 
     let fetch_results = fetcher
         .do_for_all_remote_nodes(remotes.clone().into_iter(), fetch_available_updates)
         .await;
 
-    let mut content = get_cached_summary_or_default()?;
+    let mut content = get_cached_summary_or_default(get_cache_path(app))?;
 
     for (remote_name, result) in fetch_results.remote_results {
         let entry = content
@@ -267,20 +291,24 @@ pub async fn refresh_update_summary_cache(remotes: Vec<Remote>) -> Result<(), Er
         }
     }
 
-    let options = proxmox_product_config::default_create_options();
-    proxmox_sys::fs::replace_file(UPDATE_CACHE, &serde_json::to_vec(&content)?, options, true)?;
+    proxmox_sys::fs::replace_file(
+        get_cache_path(app),
+        &serde_json::to_vec(&content)?,
+        app.default_create_options(),
+        true,
+    )?;
 
     Ok(())
 }
 
 async fn fetch_available_updates(
-    _context: (),
+    client_factory: Arc<dyn ClientFactory + Send + Sync>,
     remote: Remote,
     node: String,
 ) -> Result<NodeUpdateInfo, Error> {
     match remote.ty {
         RemoteType::Pve => {
-            let client = connection::make_pve_client(&remote)?;
+            let client = client_factory.make_pve_client(&remote)?;
 
             let updates = client
                 .list_available_updates(&node)
@@ -312,7 +340,7 @@ async fn fetch_available_updates(
             })
         }
         RemoteType::Pbs => {
-            let client = connection::make_pbs_client(&remote)?;
+            let client = client_factory.make_pbs_client(&remote)?;
             let updates = client.list_available_updates().await?;
 
             let versions = client.get_package_versions().await?;
-- 
2.47.3





  parent reply	other threads:[~2026-01-29 13:44 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-01-29 13:44 [RFC datacenter-manager/proxmox 0/7] inject application context via rpcenv for easier integration testing Lukas Wagner
2026-01-29 13:44 ` [RFC proxmox 1/1] router: rpc environment: allow to provide a application-specific context handle via rpcenv Lukas Wagner
2026-01-29 13:44 ` [RFC datacenter-manager 1/6] connection: store client factory in an Arc and add public getter Lukas Wagner
2026-01-29 13:44 ` [RFC datacenter-manager 2/6] parallel fetcher: allow to use custom client factory Lukas Wagner
2026-01-29 13:44 ` [RFC datacenter-manager 3/6] introduce PdmApplication struct and inject it during API server startup Lukas Wagner
2026-01-29 13:44 ` Lukas Wagner [this message]
2026-01-29 13:44 ` [RFC datacenter-manager 5/6] tests: add captured responses for integration tests Lukas Wagner
2026-01-29 13:44 ` [RFC datacenter-manager 6/6] tests: add basic integration tests for the remote updates API Lukas Wagner
2026-02-03 11:02 ` [RFC datacenter-manager/proxmox 0/7] inject application context via rpcenv for easier integration testing Robert Obkircher

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260129134418.307552-6-l.wagner@proxmox.com \
    --to=l.wagner@proxmox.com \
    --cc=pdm-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal