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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.