From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 4B8781FF13F for ; Thu, 29 Jan 2026 14:44:16 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 7B81E566C; Thu, 29 Jan 2026 14:44:40 +0100 (CET) From: Lukas Wagner 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 Message-ID: <20260129134418.307552-6-l.wagner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260129134418.307552-1-l.wagner@proxmox.com> References: <20260129134418.307552-1-l.wagner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1769694200221 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.037 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: RHYQYDUKFC55PEXITEK5ORUTJ5OKVKJ3 X-Message-ID-Hash: RHYQYDUKFC55PEXITEK5ORUTJ5OKVKJ3 X-MailFrom: l.wagner@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Datacenter Manager development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Signed-off-by: Lukas Wagner --- 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 { }, )] /// Return the cached update information about a remote. -pub fn get_updates(remote: String) -> Result { - let update_summary = get_available_updates_for_remote(&remote)?; +pub fn get_updates( + remote: String, + rpcenv: &mut dyn RpcEnvironment, +) -> Result { + 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 { + 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 Result Result { - 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, Error> { - let (config, _digest) = pdm_config::remotes::config()?; +async fn apt_update_available( + remote: String, + node: String, + rpcenv: &mut dyn RpcEnvironment, +) -> Result, 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 Result { - let (config, _digest) = pdm_config::remotes::config()?; +pub async fn apt_update_database( + remote: String, + node: String, + rpcenv: &mut dyn RpcEnvironment, +) -> Result { + 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 { - 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 { - 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) -> 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, +} impl RemoteUpdateRefreshTask { + fn new(app: Arc) -> 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 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, 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 { +pub async fn update_apt_database( + app: &PdmApplication, + remote: &Remote, + node: &str, +) -> Result { 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 { - 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 Result { +pub async fn get_changelog( + app: &PdmApplication, + remote: &Remote, + node: &str, + package: String, +) -> Result { 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 { - let (config, _digest) = pdm_config::remotes::config()?; +pub fn get_available_updates_summary(app: &PdmApplication) -> Result { + 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 { } /// Return cached update information from specific remote -pub fn get_available_updates_for_remote(remote: &str) -> Result { - let (config, _digest) = pdm_config::remotes::config()?; +pub fn get_available_updates_for_remote( + app: &PdmApplication, + remote: &str, +) -> Result { + 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 Result { - match File::open(UPDATE_CACHE) { +fn get_cache_path(app: &PdmApplication) -> PathBuf { + app.cache_path().join(CACHE_FILENAME) +} + +fn get_cached_summary_or_default>(cache_path: P) -> Result { + 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 { } 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) -> Result<(), Error> { - let fetcher = ParallelFetcher::new(()); +pub async fn refresh_update_summary_cache( + app: &PdmApplication, + remotes: Vec, +) -> 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) -> 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, remote: Remote, node: String, ) -> Result { 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