From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id D829C1FF0E1 for ; Thu, 27 Aug 2026 13:43:08 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 9933E214AB; Thu, 27 Aug 2026 13:43:06 +0200 (CEST) From: Lukas Wagner To: pdm-devel@lists.proxmox.com Subject: [PATCH datacenter-manager v3 10/21] context: establish PdmApplication object Date: Thu, 27 Aug 2026 13:42:33 +0200 Message-ID: <20260827114244.424784-11-l.wagner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260827114244.424784-1-l.wagner@proxmox.com> References: <20260827114244.424784-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: 1787830960690 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.594 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust 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: ICTKXSN5NVHCHEBGNMIXNH3MJ7KYRIYN X-Message-ID-Hash: ICTKXSN5NVHCHEBGNMIXNH3MJ7KYRIYN 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: PdmApplication bundles the client factory, remote config, subscription key config, and product config behind a single cloneable handle. This replaces the growing set of independent global statics with one object that can be assembled differently for production, the fake-remote feature, and integration tests. It is reachable through a new context::pdm_application() accessor, built by the same context::init() call that already seeds the existing remote/subscription config globals. Later commits in this series inject it through the API handlers via the router's shared state instead of this accessor. Signed-off-by: Lukas Wagner --- server/src/context/mod.rs | 102 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 4 deletions(-) diff --git a/server/src/context/mod.rs b/server/src/context/mod.rs index 77c84f98..3e89bceb 100644 --- a/server/src/context/mod.rs +++ b/server/src/context/mod.rs @@ -2,9 +2,12 @@ //! //! Make sure to call `init` *once* when starting up the API server. -use std::sync::Arc; +use std::sync::{Arc, OnceLock}; use anyhow::Error; + +use proxmox_product_config::{ProductConfig, ProductConfigParams}; + use pdm_config::{remotes::RemoteConfig, subscriptions::SubscriptionKeyConfig}; use crate::connection::{self, ClientFactory}; @@ -15,16 +18,36 @@ mod faked_remotes; #[cfg(not(remote_config = "faked"))] mod default; +static APP: OnceLock = OnceLock::new(); + /// Dependency-inject concrete implementations needed at runtime. -pub fn init() -> Result<(), Error> { +pub fn init() -> Result { let factory = context_factory()?; - pdm_config::subscriptions::init(factory.make_subscription_key_config()?); + let app = factory.make_pdm_application()?; + APP.set(app.clone()) + .map_err(|_| anyhow::format_err!("context::init was already called"))?; + + // NOTE: This is technically a second, independent instance of the remote config. + // Long-term, we'd like to get rid of the global instance handle in pdm_config::remotes + // anyway, and the implementation is stateless, so having this second + // instance is not an issue *currently*. pdm_config::remotes::init(factory.make_remote_config()?); + pdm_config::subscriptions::init(factory.make_subscription_key_config()?); + // FIXME: Rather let connection use an Application context object from here connection::init(factory.make_client_factory()?); - Ok(()) + Ok(app) +} + +/// Retrieve a handle to [`PdmApplication`] for this server. +/// +/// Prefer to retrieve this via the API handler using [`proxmox_router::State`]. +pub fn pdm_application() -> PdmApplication { + APP.get() + .expect("context::init was not called to set up the application context object") + .clone() } pub trait ContextFactory { @@ -43,6 +66,30 @@ pub trait ContextFactory { pdm_config::subscriptions::DefaultSubscriptionKeyConfig, )) } + + fn make_product_config(&self) -> Result { + let product_config = ProductConfig::new(ProductConfigParams { + api_user: pdm_config::api_user()?, + priv_user: pdm_config::priv_user()?, + config_dir: pdm_buildcfg::configdir!("/").into(), + state_dir: pdm_buildcfg::statedir!("/").into(), + run_dir: pdm_buildcfg::rundir!("/").into(), + cache_dir: pdm_buildcfg::PDM_CACHE_DIR.into(), + }); + + Ok(product_config) + } + + fn make_pdm_application(&self) -> Result { + Ok(PdmApplication { + inner: Arc::new(PdmApplicationInner { + client_factory: self.make_client_factory()?, + remote_config: self.make_remote_config()?, + subscription_key_config: self.make_subscription_key_config()?, + product_config: self.make_product_config()?, + }), + }) + } } fn context_factory() -> Result { @@ -55,3 +102,50 @@ fn context_factory() -> Result { Ok(default::DefaultContextFactory) } } + +/// Application context handle. +/// +/// This type gives access to dependency-injected implementations and general product +/// configuration. +/// +/// This implements [`Clone`] and can be cheaply copied (it contains a single `Arc`). +#[derive(Clone)] +pub struct PdmApplication { + inner: Arc, +} + +impl PdmApplication { + /// Get a handle to the [`ClientFactory`] as an [`Arc`]. + /// + /// Prefer to use [`Self::client_factory`] if possible. + pub fn client_factory_shared(&self) -> Arc { + Arc::clone(&self.inner.client_factory) + } + + /// Get a reference to the [`ClientFactory`]. + pub fn client_factory(&self) -> &(dyn ClientFactory + Send + Sync) { + self.inner.client_factory.as_ref() + } + + /// Get a reference to the [`RemoteConfig`]. + pub fn remote_config(&self) -> &(dyn RemoteConfig + Send + Sync) { + self.inner.remote_config.as_ref() + } + + /// Get a reference to the [`SubscriptionKeyConfig`]. + pub fn subscription_key_config(&self) -> &(dyn SubscriptionKeyConfig + Send + Sync) { + self.inner.subscription_key_config.as_ref() + } + + /// Get a reference to the [`ProductConfig`]. + pub fn product_config(&self) -> &ProductConfig { + &self.inner.product_config + } +} + +struct PdmApplicationInner { + client_factory: Arc, + remote_config: Box, + subscription_key_config: Box, + product_config: ProductConfig, +} -- 2.47.3