From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 1F5C31FF13F for ; Thu, 29 Jan 2026 14:44:37 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 1A4185794; Thu, 29 Jan 2026 14:45:03 +0100 (CET) From: Lukas Wagner To: pdm-devel@lists.proxmox.com Subject: [RFC datacenter-manager 3/6] introduce PdmApplication struct and inject it during API server startup Date: Thu, 29 Jan 2026 14:44:15 +0100 Message-ID: <20260129134418.307552-5-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: 1769694200045 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: L43675SELD6X6JSVDHBFLA4UAZVYTXY2 X-Message-ID-Hash: L43675SELD6X6JSVDHBFLA4UAZVYTXY2 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: This struct is used as a container for elemental runtime configuration like base directories and permissions, as well as to aid us in using dependency injection for things that need to be mocked in tests (e.g. the client factory). It is injected into the rest server; a handle to it can be retrieved from rpcenv that is passed to every API request handler. Signed-off-by: Lukas Wagner --- server/src/bin/proxmox-datacenter-api/main.rs | 7 +- .../bin/proxmox-datacenter-privileged-api.rs | 7 +- server/src/context.rs | 70 +++++++++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/server/src/bin/proxmox-datacenter-api/main.rs b/server/src/bin/proxmox-datacenter-api/main.rs index 524a4b51..e674e763 100644 --- a/server/src/bin/proxmox-datacenter-api/main.rs +++ b/server/src/bin/proxmox-datacenter-api/main.rs @@ -1,3 +1,4 @@ +use std::any::Any; use std::net::{IpAddr, Ipv6Addr, SocketAddr}; use std::path::Path; use std::pin::pin; @@ -28,6 +29,7 @@ use proxmox_auth_api::api::assemble_csrf_prevention_token; use server::auth; use server::auth::csrf::csrf_secret; +use server::context::PdmApplication; use server::metric_collection; use server::resource_cache; use server::task_utils; @@ -151,6 +153,8 @@ async fn run(debug: bool) -> Result<(), Error> { let indexpath = Path::new(pdm_buildcfg::JS_DIR).join("index.hbs"); + let app = Arc::new(PdmApplication::default()); + let config = ApiConfig::new(pdm_buildcfg::JS_DIR, RpcEnvironmentType::PUBLIC) .privileged_addr( std::os::unix::net::SocketAddr::from_pathname( @@ -183,7 +187,8 @@ async fn run(debug: bool) -> Result<(), Error> { Some(dir_opts), Some(file_opts), &mut command_sock, - )?; + )? + .with_application_context(Arc::clone(&app) as Arc); let rest_server = RestServer::new(config); let redirector = proxmox_rest_server::Redirector::new(); diff --git a/server/src/bin/proxmox-datacenter-privileged-api.rs b/server/src/bin/proxmox-datacenter-privileged-api.rs index 6b490f2b..7e8cf1d2 100644 --- a/server/src/bin/proxmox-datacenter-privileged-api.rs +++ b/server/src/bin/proxmox-datacenter-privileged-api.rs @@ -1,5 +1,6 @@ use std::path::Path; use std::pin::pin; +use std::sync::Arc; use anyhow::{bail, format_err, Context as _, Error}; use futures::*; @@ -15,6 +16,7 @@ use proxmox_router::RpcEnvironmentType; use proxmox_sys::fs::CreateOptions; use server::auth; +use server::context::PdmApplication; use pdm_buildcfg::configdir; @@ -131,6 +133,8 @@ async fn run() -> Result<(), Error> { let dir_opts = CreateOptions::new().owner(api_user.uid).group(api_user.gid); let file_opts = CreateOptions::new().owner(api_user.uid).group(api_user.gid); + let app = Arc::new(PdmApplication::default()); + let config = ApiConfig::new(pdm_buildcfg::JS_DIR, RpcEnvironmentType::PRIVILEGED) .auth_handler_func(|h, m| Box::pin(auth::check_auth(h, m))) .formatted_router(&["api2"], &server::api::ROUTER) @@ -145,7 +149,8 @@ async fn run() -> Result<(), Error> { Some(dir_opts), Some(file_opts), &mut command_sock, - )?; + )? + .with_application_context(app); let rest_server = RestServer::new(config); proxmox_rest_server::init_worker_tasks(pdm_buildcfg::PDM_LOG_DIR_M!().into(), file_opts)?; diff --git a/server/src/context.rs b/server/src/context.rs index 8c841eec..f9566a58 100644 --- a/server/src/context.rs +++ b/server/src/context.rs @@ -2,10 +2,15 @@ //! //! Make sure to call `init` *once* when starting up the API server. +use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Error; +use pdm_config::remotes::{DefaultRemoteConfig, RemoteConfig}; +use proxmox_router::RpcEnvironment; +use proxmox_sys::fs::CreateOptions; + use crate::connection; /// Dependency-inject production remote-config implementation and remote client factory @@ -42,3 +47,68 @@ pub fn init() -> Result<(), Error> { Ok(()) } + +/// Retrieve the [`PdmApplication`] instance from the `rpcenv` handle. +/// +/// Panics: +/// - This function will panic if the rpcenv was never initialized with a [`PdmApplication`] +/// handle. +pub fn get_application(rpcenv: &dyn RpcEnvironment) -> Arc { + rpcenv + .application_context() + .expect("Application context has not been set up") + .downcast() + .expect("Could not downcast to PdmApplication") +} + +pub struct PdmApplication { + // TODO: These should not be public, but only be accessed through getters. + // TODO: Consider a builder to construct this struct. + pub remote_config: Arc, + pub client_factory: Arc, + + pub config_path: PathBuf, + pub cache_path: PathBuf, + + pub default_create_options: CreateOptions, +} + +impl PdmApplication { + // FIXME: What kind of methods should be here or in 'sub-objects' needs to be + // determined. What we don't want is some universal 'god object' that kind of + // does everything at once. + + pub fn remote_config(&self) -> Arc { + Arc::clone(&self.remote_config) + } + + pub fn client_factory(&self) -> Arc { + Arc::clone(&self.client_factory) + } + + pub fn config_path(&self) -> &Path { + &self.config_path + } + + pub fn cache_path(&self) -> &Path { + &self.cache_path + } + + pub fn default_create_options(&self) -> CreateOptions { + self.default_create_options + } +} + +impl Default for PdmApplication { + /// Only for convenience for now. + fn default() -> Self { + Self { + remote_config: Arc::new(DefaultRemoteConfig), + client_factory: connection::client_factory(), + config_path: pdm_buildcfg::CONFIGDIR.into(), + cache_path: pdm_buildcfg::PDM_CACHE_DIR.into(), + // TODO: These should come from somewhere else. + default_create_options: proxmox_product_config::default_create_options(), + } + } +} -- 2.47.3