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
next prev 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