* [RFC datacenter-manager/proxmox 0/7] inject application context via rpcenv for easier integration testing
@ 2026-01-29 13:44 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
` (7 more replies)
0 siblings, 8 replies; 9+ messages in thread
From: Lukas Wagner @ 2026-01-29 13:44 UTC (permalink / raw)
To: pdm-devel
TL;DR: Inject essential runtime config, client factory, etc. as an application
context object and allow to retrieve this object easily in an API handler via
rpcenv. This allows us directly call API handler implementations from
integration tests. The aim is to make it easier to write good tests on a larger
level.
## Introduction
Considering the different stages of automated software testing, unit testing
(test small software components in isolation) integration testing (test
multiple components to together, specifically their interactions) and
end-to-end testing (test the entire application in a context as close to
production as possible), I've found that the middle one, integration testing is
a very challenging one in our stack.
I've found that the challenges with integration testing mostly stem from the
following:
- "hardcoded" (as in, determined by some constant or literally
hard-coded) assumptions about storage paths (config, state, caches) and
users/permissions. This makes it challenging to call into the component
from a test running as normal user, e.g. from a regular `cargo test`.
- use of global/static instances (examples: Worker task context,
proxmox-product-config, client factory in PDM...) in application code and
shared crates. While this can be okay for things that are truly global (e.g.
logging), it often hinders testing and especially test isolation due to hidden
dependencies between test cases and some potential internal state of the global
instance. This is one of the common causes of flaky tests. Also, the setup of
these global instances in test cases is always a bit awkward, since the order
of test execution is not defined (and might actually run in parallel), so some
kind of synchronization between the test cases is necessary. Furthermore,
certain test cases might require a *different* setup for these global instances
(example: client factory that should produce a different kind of mocked PVE
client); but since we usually put these in OnceLocks, one has to put these
tests into a separate test binary.
- tight coupling between major subsystems (e.g. one part calling into the
other without any clear boundary or abstraction via callbacks or traits)
With regards to PDM, there are a couple of things that need considering when testing:
- Filesystem access to config, state and caches
- Interactions with remotes via their API
I feel like if we find a sensible way to abstract these for tests, we can
resonably cover a huge amount of the code in the backend.
## Implementation
The core idea I want to discuss in this RFC is to provide a context/application
object ('struct PdmApplication') and "injecting" it into out API handlers via
the already existing rpcenv variable.
This new context object would give access to:
- base paths for caches, config, state
- file permissions, user/group
- client factory
- depending on what we need, potentially other things
So, for instance, instead of calling into the `connection` module to create a
PVE client, one could retrieve the client factory instance from the application
object and create the application from that:
So the following
let client = connection::make_pve_client(...)
becomes
// app is of type `PdmApplication`
let client = app.client_factory().make_pve_client(...)
And
let config_path = configdir!("foo.cfg")
becomes
let config_path = app.config_path().join("foo.cfg")
NOTE: The exact structure of this object and what kind of methods it provides
is not fixed yet.
## Where does `PdmApplication` come from?
For the regular API daemon, the PdmApplication context object is set up during
the daemon startup routine.
A handle to it is then associated with the REST server and stored in the
RpcEnvironment that is passed to the API handlers. In it's current form this
entails some ugly `dyn Any` hacks, but I think it's not too bad. Any API
handler can retrieve the application object from rpcenv, e.g. like this:
// API handler implementation
async fn apt_get_changelog(
remote: String,
node: String,
options: APTGetChangelogOptions,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<String, Error> {
// `get_application` simply does the downcast from `Any` to the correct type.
// RpcEnvironment has to hold it as `Any` since the `PdmApplication` type is
// of course specific to the application itself.
//
// NOTE: app is of type Arc<PdmApplication>
let app = context::get_application(rpcenv);
// For configs that might to be mocked for tests, the app object could
// also provide some form of accessor:
let (config, _digest) = app.remote_config().config()?;
let remote = get_remote(&config, &remote)?;
remote_updates::get_changelog(&app, remote, &node, options.name).await
}
This application handle would need to be propagated down the call stack, either
as a &PdmApplication or a Arc<PdmApplication>:
// actual implementation of the functionality somewhere else in the codebase
pub async fn get_changelog(
app: &PdmApplication,
remote: &Remote,
node: &str,
package: String,
) -> Result<String, Error> {
...
let client = app.client_factory().make_pve_client(remote)?;
...
}
Code that is executed independently from API handlers, e.g. periodic tasks,
would receive their application handle during setup (as a clone of the
'original' Arc<PdmApplication> that was injected into the REST server).
This general pattern of injecting application state/context via a parameter
to the handler is something that is also common in other Rust-based
web framworks, e.g. [actix-web] and [axum].
## Interaction with shared crates
Quite a few of our shared crates in proxmox.git have some form of static/global
context that has to be initialized, for instance:
- proxmox-notify
- proxmox-rest-server (worker task setup)
- proxmox-product-config
- proxmox-access-control
I think long-term, if possible, we should try to avoid having static context in
crates completely. For instance, at some point I'll probably refactor
proxmox-notify so that the context is always passed along with any call to the
crate, instead of setting it up statically. As a stop-gap, this context can
still be static in the *application*, but with everything else I mentioned in
this RFC so far, I think that it should in some form be derived from the
general PdmApplication context object.
One thing that could work is implementing a crate-specific context trait for the
application object itself and then passing the application itself:
impl proxmox_notify::Context for PdmApplication {
...
}
proxmox_notify::send(&app, ¬ification)?;
Or, alternatively, the `app` object would be used to construct some kind of other
context object for the crate:
struct PdmNotifyContext {
...
}
impl proxmox_notify::Context for PdmNotifyContext {
...
}
impl PdmNotifyContext {
fn from_app(app: &PdmApplication) {
...
}
}
let notify_context = PdmNotifyContext::from_app(app);
proxmox_notify::send(¬ify_context, ¬ification)?;
I have not yet written any code for these shared crate setups, but I think the
general pattern *should* work.
What I'm not so sure about yet and where I have not done any experiments is the
case where the entire API handler is defined in a shared crate. These of course
cannot access the product-specific struct. Many of these rely on some form of
static initialization, either directly or indirectly (e.g.
proxmox-product-config).
I'd be happy about any ideas for these cases.
Personally I'm not a big fan of defining the entire API handlers in the shared
crate, I rather prefer having the handler definitions in the application code
and having this handler call *into* the shared crate - I think this gives more
flexibility in case there need to be some application-specific changes (e.g.
additional parameters, different types, etc.). Also in this case it would be
easy to pass along an additional context object along with the call.
## Summary
Pros:
- We can reasonably test subsystems in isolation with the comfort of a simple `cargo test`
- I think this RFC shows that it's definitely possible to adapt to this new pattern
gradually without having to refactor huge amounts of existing code at once.
Risks:
- Increased complexity
- Having to pass some kind of application handle down the callstack
could be considered quite invasive.
- Having to deal with Box'd/Arc'd trait objects (e.g. the client factory)
can be a bit annoying
- The application context object
- While this approach does not require us to commit fully right from the start, it could
be quite awkward if we have "the old approach" and the "new approach" in parallel.
So it would be important that we agree on the sensibility of this approach and then
consequently use it for new features and then over time gradually introduce it
into the existing code.
The patches submitted in this patch series are of course still work in
progress; their main aim is to demonstrate that viability of the concept.
I'd be very happy to get some feedback on the general idea and of course also
the PoC implementation (but don't get too caught up in naming, missing doc
comments, etc - as I said, everything is still work in progress).
## References:
[actix-web]: https://actix.rs/docs/application/#state
[axum]: https://docs.rs/axum/latest/axum/#sharing-state-with-handlers
proxmox:
Lukas Wagner (1):
router: rpc environment: allow to provide a application-specific
context handle via rpcenv
proxmox-rest-server/src/api_config.rs | 10 ++++++++++
proxmox-rest-server/src/environment.rs | 6 +++++-
proxmox-router/src/cli/environment.rs | 6 ++++++
proxmox-router/src/rpc_environment.rs | 5 ++++-
4 files changed, 25 insertions(+), 2 deletions(-)
proxmox-datacenter-manager:
Lukas Wagner (6):
connection: store client factory in an Arc and add public getter
parallel fetcher: allow to use custom client factory
introduce PdmApplication struct and inject it during API server
startup
remote updates: use PdmApplication object to derive paths, permissions
and client factory
tests: add captured responses for integration tests
tests: add basic integration tests for the remote updates API
server/src/api/pve/mod.rs | 11 +-
server/src/api/remote_updates.rs | 54 +-
server/src/bin/proxmox-datacenter-api/main.rs | 9 +-
.../tasks/remote_tasks.rs | 2 +
.../tasks/remote_updates.rs | 22 +-
.../bin/proxmox-datacenter-privileged-api.rs | 7 +-
server/src/connection.rs | 29 +-
server/src/context.rs | 74 +-
server/src/lib.rs | 3 +-
.../src/metric_collection/collection_task.rs | 2 +-
server/src/parallel_fetcher.rs | 24 +-
server/src/remote_updates.rs | 90 ++-
server/src/test_support/mod.rs | 3 +-
server/tests/api_responses/pve/apt_repos.json | 469 +++++++++++
.../tests/api_responses/pve/apt_update.json | 638 +++++++++++++++
.../tests/api_responses/pve/apt_versions.json | 736 ++++++++++++++++++
.../api_responses/pve/node_subscription.json | 6 +
server/tests/test_remote_updates.rs | 288 +++++++
18 files changed, 2394 insertions(+), 73 deletions(-)
create mode 100644 server/tests/api_responses/pve/apt_repos.json
create mode 100644 server/tests/api_responses/pve/apt_update.json
create mode 100644 server/tests/api_responses/pve/apt_versions.json
create mode 100644 server/tests/api_responses/pve/node_subscription.json
create mode 100644 server/tests/test_remote_updates.rs
Summary over all repositories:
22 files changed, 2419 insertions(+), 75 deletions(-)
--
Generated by murpp 0.9.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [RFC proxmox 1/1] router: rpc environment: allow to provide a application-specific context handle via rpcenv
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 ` 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
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Lukas Wagner @ 2026-01-29 13:44 UTC (permalink / raw)
To: pdm-devel
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
proxmox-rest-server/src/api_config.rs | 10 ++++++++++
proxmox-rest-server/src/environment.rs | 6 +++++-
proxmox-router/src/cli/environment.rs | 6 ++++++
proxmox-router/src/rpc_environment.rs | 5 ++++-
4 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/proxmox-rest-server/src/api_config.rs b/proxmox-rest-server/src/api_config.rs
index 87d6566c..faaec2d3 100644
--- a/proxmox-rest-server/src/api_config.rs
+++ b/proxmox-rest-server/src/api_config.rs
@@ -1,3 +1,4 @@
+use std::any::Any;
use std::collections::HashMap;
use std::future::Future;
use std::io;
@@ -39,6 +40,8 @@ pub struct ApiConfig {
#[cfg(feature = "templates")]
templates: templates::Templates,
+
+ pub(crate) context: Option<Arc<dyn Any + Send + Sync>>,
}
impl ApiConfig {
@@ -66,6 +69,7 @@ impl ApiConfig {
index_handler: None,
privileged_addr: None,
auth_cookie_name: None,
+ context: None,
#[cfg(feature = "templates")]
templates: templates::Templates::with_escape_fn(),
@@ -111,6 +115,12 @@ impl ApiConfig {
self.index_handler(IndexHandler::from_fn(func))
}
+ /// Inject application context that later be retrieved via `[RpcEnvironment::application_context()]`.
+ pub fn with_application_context(mut self, context: Arc<dyn Any + Send + Sync>) -> Self {
+ self.context = Some(context);
+ self
+ }
+
pub(crate) async fn get_index(
&self,
rest_env: RestEnvironment,
diff --git a/proxmox-rest-server/src/environment.rs b/proxmox-rest-server/src/environment.rs
index c349c324..636bdca2 100644
--- a/proxmox-rest-server/src/environment.rs
+++ b/proxmox-rest-server/src/environment.rs
@@ -1,5 +1,5 @@
-use std::net::SocketAddr;
use std::sync::Arc;
+use std::{any::Any, net::SocketAddr};
use serde_json::{json, Value};
@@ -89,4 +89,8 @@ impl RpcEnvironment for RestEnvironment {
fn get_client_ip(&self) -> Option<SocketAddr> {
self.client_ip
}
+
+ fn application_context(&self) -> Option<Arc<dyn Any + Send + Sync>> {
+ self.api_config().context.as_ref().map(Arc::clone)
+ }
}
diff --git a/proxmox-router/src/cli/environment.rs b/proxmox-router/src/cli/environment.rs
index 3d3dec19..498eedc2 100644
--- a/proxmox-router/src/cli/environment.rs
+++ b/proxmox-router/src/cli/environment.rs
@@ -1,5 +1,6 @@
use std::any::{Any, TypeId};
use std::collections::HashMap;
+use std::sync::Arc;
use serde_json::Value;
@@ -81,4 +82,9 @@ impl RpcEnvironment for CliEnvironment {
fn get_auth_id(&self) -> Option<String> {
self.auth_id.clone()
}
+
+ fn application_context(&self) -> Option<Arc<dyn Any + Send + Sync>> {
+ // FIXME: set up context for cli env as well.
+ None
+ }
}
diff --git a/proxmox-router/src/rpc_environment.rs b/proxmox-router/src/rpc_environment.rs
index 8ce2d99d..37140b91 100644
--- a/proxmox-router/src/rpc_environment.rs
+++ b/proxmox-router/src/rpc_environment.rs
@@ -1,4 +1,4 @@
-use std::any::Any;
+use std::{any::Any, sync::Arc};
use serde_json::Value;
@@ -45,6 +45,9 @@ pub trait RpcEnvironment: Any + AsAny + Send {
fn get_client_ip(&self) -> Option<std::net::SocketAddr> {
None // dummy no-op implementation, as most environments don't need this
}
+
+ /// Return application context that was previously injected.
+ fn application_context(&self) -> Option<Arc<dyn Any + Send + Sync>>;
}
/// Environment Type
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [RFC datacenter-manager 1/6] connection: store client factory in an Arc and add public getter
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 ` Lukas Wagner
2026-01-29 13:44 ` [RFC datacenter-manager 2/6] parallel fetcher: allow to use custom client factory Lukas Wagner
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Lukas Wagner @ 2026-01-29 13:44 UTC (permalink / raw)
To: pdm-devel
This will be useful to truly ensure that everybody is using the same
factory.
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
server/src/connection.rs | 29 ++++++++++++-------
server/src/context.rs | 4 ++-
.../src/metric_collection/collection_task.rs | 2 +-
3 files changed, 23 insertions(+), 12 deletions(-)
diff --git a/server/src/connection.rs b/server/src/connection.rs
index 7e366719..f144f9cf 100644
--- a/server/src/connection.rs
+++ b/server/src/connection.rs
@@ -26,7 +26,7 @@ use pve_api_types::client::PveClientImpl;
use crate::pbs_client::PbsClient;
-static INSTANCE: OnceLock<Box<dyn ClientFactory + Send + Sync>> = OnceLock::new();
+static INSTANCE: OnceLock<Arc<dyn ClientFactory + Send + Sync>> = OnceLock::new();
/// Connection Info returned from [`prepare_connect_client`]
struct ConnectInfo {
@@ -396,7 +396,8 @@ impl ClientFactory for DefaultClientFactory {
}
}
-fn instance() -> &'static (dyn ClientFactory + Send + Sync) {
+/// Get a handle to the global client factory (as an reference).
+pub fn client_factory_ref() -> &'static (dyn ClientFactory + Send + Sync) {
// Not initializing the connection factory instance is
// entirely in our responsibility and not something we can recover from,
// so it should be okay to panic in this case.
@@ -406,9 +407,17 @@ fn instance() -> &'static (dyn ClientFactory + Send + Sync) {
.as_ref()
}
+/// Get a handle to the global client factory (as an Arc).
+pub fn client_factory() -> Arc<dyn ClientFactory + Send + Sync> {
+ // Not initializing the connection factory instance is
+ // entirely in our responsibility and not something we can recover from,
+ // so it should be okay to panic in this case.
+ Arc::clone(INSTANCE.get().expect("client factory instance not set"))
+}
+
/// Create a new API client for PVE remotes
pub fn make_pve_client(remote: &Remote) -> Result<Arc<PveClient>, Error> {
- instance().make_pve_client(remote)
+ client_factory_ref().make_pve_client(remote)
}
/// Create a new API client for PVE remotes, but for a specific endpoint
@@ -416,21 +425,21 @@ pub fn make_pve_client_with_endpoint(
remote: &Remote,
target_endpoint: Option<&str>,
) -> Result<Arc<PveClient>, Error> {
- instance().make_pve_client_with_endpoint(remote, target_endpoint)
+ client_factory_ref().make_pve_client_with_endpoint(remote, target_endpoint)
}
/// Create a new API client for PVE remotes and try to make it connect to a specific *node*.
pub fn make_pve_client_with_node(remote: &Remote, node: &str) -> Result<Arc<PveClient>, Error> {
- instance().make_pve_client_with_node(remote, node)
+ client_factory_ref().make_pve_client_with_node(remote, node)
}
/// Create a new API client for PBS remotes
pub fn make_pbs_client(remote: &Remote) -> Result<Box<PbsClient>, Error> {
- instance().make_pbs_client(remote)
+ client_factory_ref().make_pbs_client(remote)
}
pub fn make_raw_client(remote: &Remote) -> Result<Box<Client>, Error> {
- instance().make_raw_client(remote)
+ client_factory_ref().make_raw_client(remote)
}
/// Create a new API client for PVE remotes.
@@ -443,7 +452,7 @@ pub fn make_raw_client(remote: &Remote) -> Result<Box<Client>, Error> {
///
/// Note: currently does not support two factor authentication.
pub async fn make_pve_client_and_login(remote: &Remote) -> Result<Arc<PveClient>, Error> {
- instance().make_pve_client_and_login(remote).await
+ client_factory_ref().make_pve_client_and_login(remote).await
}
/// Create a new API client for PBS remotes.
@@ -456,13 +465,13 @@ pub async fn make_pve_client_and_login(remote: &Remote) -> Result<Arc<PveClient>
///
/// Note: currently does not support two factor authentication.
pub async fn make_pbs_client_and_login(remote: &Remote) -> Result<Box<PbsClient>, Error> {
- instance().make_pbs_client_and_login(remote).await
+ client_factory_ref().make_pbs_client_and_login(remote).await
}
/// Initialize the [`ClientFactory`] instance.
///
/// Will panic if the instance has already been set.
-pub fn init(instance: Box<dyn ClientFactory + Send + Sync>) {
+pub fn init(instance: Arc<dyn ClientFactory + Send + Sync>) {
if INSTANCE.set(instance).is_err() {
panic!("connection factory instance already set");
}
diff --git a/server/src/context.rs b/server/src/context.rs
index c5da0afd..8c841eec 100644
--- a/server/src/context.rs
+++ b/server/src/context.rs
@@ -2,6 +2,8 @@
//!
//! Make sure to call `init` *once* when starting up the API server.
+use std::sync::Arc;
+
use anyhow::Error;
use crate::connection;
@@ -10,7 +12,7 @@ use crate::connection;
#[allow(dead_code)]
fn default_remote_setup() {
pdm_config::remotes::init(Box::new(pdm_config::remotes::DefaultRemoteConfig));
- connection::init(Box::new(connection::DefaultClientFactory));
+ connection::init(Arc::new(connection::DefaultClientFactory));
}
/// Dependency-inject concrete implementations needed at runtime.
diff --git a/server/src/metric_collection/collection_task.rs b/server/src/metric_collection/collection_task.rs
index cc1a460e..00ab2cc0 100644
--- a/server/src/metric_collection/collection_task.rs
+++ b/server/src/metric_collection/collection_task.rs
@@ -553,7 +553,7 @@ pub(super) mod tests {
// TODO: the client factory is currently stored in a OnceLock -
// we can only set it from one test... Ideally we'd like to have the
// option to set it in every single test if needed - task/thread local?
- connection::init(Box::new(TestClientFactory { now }));
+ connection::init(Arc::new(TestClientFactory { now }));
});
now
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [RFC datacenter-manager 2/6] parallel fetcher: allow to use custom client factory
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 ` Lukas Wagner
2026-01-29 13:44 ` [RFC datacenter-manager 3/6] introduce PdmApplication struct and inject it during API server startup Lukas Wagner
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Lukas Wagner @ 2026-01-29 13:44 UTC (permalink / raw)
To: pdm-devel
This will become useful later when the actual client factory is a custom
mocked one that we use for tests.
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
.../tasks/remote_tasks.rs | 2 ++
server/src/parallel_fetcher.rs | 24 +++++++++++++++++--
2 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/server/src/bin/proxmox-datacenter-api/tasks/remote_tasks.rs b/server/src/bin/proxmox-datacenter-api/tasks/remote_tasks.rs
index c71a0894..7ce37631 100644
--- a/server/src/bin/proxmox-datacenter-api/tasks/remote_tasks.rs
+++ b/server/src/bin/proxmox-datacenter-api/tasks/remote_tasks.rs
@@ -264,10 +264,12 @@ async fn fetch_remotes(
remotes: Vec<Remote>,
cache_state: Arc<State>,
) -> (Vec<TaskCacheItem>, NodeFetchSuccessMap) {
+ // FIXME: Use constructor or builder pattern here...
let fetcher = ParallelFetcher {
max_connections: MAX_CONNECTIONS,
max_connections_per_remote: CONNECTIONS_PER_PVE_REMOTE,
context: cache_state,
+ client_factory: connection::client_factory(),
};
let fetch_results = fetcher
diff --git a/server/src/parallel_fetcher.rs b/server/src/parallel_fetcher.rs
index 58ca1f55..f123460d 100644
--- a/server/src/parallel_fetcher.rs
+++ b/server/src/parallel_fetcher.rs
@@ -14,15 +14,18 @@ use tokio::{
task::JoinSet,
};
-use crate::connection;
+use crate::connection::{self, ClientFactory};
pub const DEFAULT_MAX_CONNECTIONS: usize = 20;
pub const DEFAULT_MAX_CONNECTIONS_PER_REMOTE: usize = 5;
+/// FIXME: Builder pattern, make fields private
pub struct ParallelFetcher<C> {
pub max_connections: usize,
pub max_connections_per_remote: usize,
pub context: C,
+
+ pub client_factory: Arc<dyn ClientFactory + Send + Sync>,
}
pub struct FetchResults<T> {
@@ -66,6 +69,20 @@ impl<C: Clone + Send + 'static> ParallelFetcher<C> {
max_connections: DEFAULT_MAX_CONNECTIONS,
max_connections_per_remote: DEFAULT_MAX_CONNECTIONS_PER_REMOTE,
context,
+ client_factory: connection::client_factory(),
+ }
+ }
+
+ // TODO: maybe add actual builder pattern.
+ pub fn new_with_client_factory(
+ context: C,
+ client_factory: Arc<dyn ClientFactory + Send + Sync>,
+ ) -> Self {
+ Self {
+ max_connections: DEFAULT_MAX_CONNECTIONS,
+ max_connections_per_remote: DEFAULT_MAX_CONNECTIONS_PER_REMOTE,
+ context,
+ client_factory,
}
}
@@ -82,6 +99,7 @@ impl<C: Clone + Send + 'static> ParallelFetcher<C> {
for remote in remotes {
let semaphore = Arc::clone(&total_connections_semaphore);
+ let client_factory = Arc::clone(&self.client_factory);
let f = func.clone();
@@ -91,6 +109,7 @@ impl<C: Clone + Send + 'static> ParallelFetcher<C> {
semaphore,
f,
self.max_connections_per_remote,
+ client_factory,
));
}
@@ -116,6 +135,7 @@ impl<C: Clone + Send + 'static> ParallelFetcher<C> {
semaphore: Arc<Semaphore>,
func: F,
max_connections_per_remote: usize,
+ client_factory: Arc<dyn ClientFactory + Send + Sync>,
) -> (String, Result<RemoteResult<T>, Error>)
where
F: Fn(C, Remote, String) -> Ft + Clone + Send + 'static,
@@ -132,7 +152,7 @@ impl<C: Clone + Send + 'static> ParallelFetcher<C> {
let remote_clone = remote.clone();
let nodes = match async move {
- let client = connection::make_pve_client(&remote_clone)?;
+ let client = client_factory.make_pve_client(&remote_clone)?;
let nodes = client.list_nodes().await?;
Ok::<Vec<ClusterNodeIndexResponse>, Error>(nodes)
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [RFC datacenter-manager 3/6] introduce PdmApplication struct and inject it during API server startup
2026-01-29 13:44 [RFC datacenter-manager/proxmox 0/7] inject application context via rpcenv for easier integration testing Lukas Wagner
` (2 preceding siblings ...)
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 ` Lukas Wagner
2026-01-29 13:44 ` [RFC datacenter-manager 4/6] remote updates: use PdmApplication object to derive paths, permissions and client factory Lukas Wagner
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Lukas Wagner @ 2026-01-29 13:44 UTC (permalink / raw)
To: pdm-devel
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 <l.wagner@proxmox.com>
---
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<dyn Any + Send + Sync>);
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<PdmApplication> {
+ 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<dyn RemoteConfig + Send + Sync>,
+ pub client_factory: Arc<dyn connection::ClientFactory + Send + Sync>,
+
+ 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<dyn RemoteConfig + Send + Sync> {
+ Arc::clone(&self.remote_config)
+ }
+
+ pub fn client_factory(&self) -> Arc<dyn connection::ClientFactory + Send + Sync> {
+ 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
^ permalink raw reply [flat|nested] 9+ messages in thread
* [RFC datacenter-manager 4/6] remote updates: use PdmApplication object to derive paths, permissions and client factory
2026-01-29 13:44 [RFC datacenter-manager/proxmox 0/7] inject application context via rpcenv for easier integration testing Lukas Wagner
` (3 preceding siblings ...)
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
2026-01-29 13:44 ` [RFC datacenter-manager 5/6] tests: add captured responses for integration tests Lukas Wagner
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Lukas Wagner @ 2026-01-29 13:44 UTC (permalink / raw)
To: pdm-devel
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
^ permalink raw reply [flat|nested] 9+ messages in thread
* [RFC datacenter-manager 5/6] tests: add captured responses for integration tests
2026-01-29 13:44 [RFC datacenter-manager/proxmox 0/7] inject application context via rpcenv for easier integration testing Lukas Wagner
` (4 preceding siblings ...)
2026-01-29 13:44 ` [RFC datacenter-manager 4/6] remote updates: use PdmApplication object to derive paths, permissions and client factory Lukas Wagner
@ 2026-01-29 13:44 ` 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
7 siblings, 0 replies; 9+ messages in thread
From: Lukas Wagner @ 2026-01-29 13:44 UTC (permalink / raw)
To: pdm-devel
Separated into its own commit so that these do not pollute the diff for
the commit adding the actual tests.
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
server/tests/api_responses/pve/apt_repos.json | 469 +++++++++++
.../tests/api_responses/pve/apt_update.json | 638 +++++++++++++++
.../tests/api_responses/pve/apt_versions.json | 736 ++++++++++++++++++
.../api_responses/pve/node_subscription.json | 6 +
4 files changed, 1849 insertions(+)
create mode 100644 server/tests/api_responses/pve/apt_repos.json
create mode 100644 server/tests/api_responses/pve/apt_update.json
create mode 100644 server/tests/api_responses/pve/apt_versions.json
create mode 100644 server/tests/api_responses/pve/node_subscription.json
diff --git a/server/tests/api_responses/pve/apt_repos.json b/server/tests/api_responses/pve/apt_repos.json
new file mode 100644
index 00000000..7e9567e1
--- /dev/null
+++ b/server/tests/api_responses/pve/apt_repos.json
@@ -0,0 +1,469 @@
+{
+ "digest" : "0ec7d07d2b0c95500dc7f10583c7721070ecd54c15531cd9fd9da9d89c281204",
+ "errors" : [],
+ "files" : [
+ {
+ "digest" : [
+ 25,
+ 16,
+ 2,
+ 105,
+ 244,
+ 161,
+ 26,
+ 50,
+ 187,
+ 214,
+ 7,
+ 97,
+ 129,
+ 101,
+ 179,
+ 13,
+ 81,
+ 128,
+ 11,
+ 47,
+ 248,
+ 216,
+ 50,
+ 97,
+ 241,
+ 154,
+ 73,
+ 246,
+ 154,
+ 173,
+ 129,
+ 83
+ ],
+ "file-type" : "list",
+ "path" : "/etc/apt/sources.list",
+ "repositories" : [
+ {
+ "Components" : [
+ "main",
+ "contrib"
+ ],
+ "Enabled" : 1,
+ "FileType" : "list",
+ "Suites" : [
+ "trixie"
+ ],
+ "Types" : [
+ "deb"
+ ],
+ "URIs" : [
+ "http://ftp.at.debian.org/debian"
+ ]
+ },
+ {
+ "Components" : [
+ "main",
+ "contrib"
+ ],
+ "Enabled" : 1,
+ "FileType" : "list",
+ "Suites" : [
+ "trixie-updates"
+ ],
+ "Types" : [
+ "deb"
+ ],
+ "URIs" : [
+ "http://ftp.at.debian.org/debian"
+ ]
+ },
+ {
+ "Comment" : " security updates\n",
+ "Components" : [
+ "main",
+ "contrib"
+ ],
+ "Enabled" : 1,
+ "FileType" : "list",
+ "Suites" : [
+ "trixie-security"
+ ],
+ "Types" : [
+ "deb"
+ ],
+ "URIs" : [
+ "http://security.debian.org"
+ ]
+ }
+ ]
+ },
+ {
+ "digest" : [
+ 20,
+ 143,
+ 139,
+ 119,
+ 109,
+ 20,
+ 12,
+ 89,
+ 123,
+ 154,
+ 12,
+ 13,
+ 60,
+ 101,
+ 189,
+ 249,
+ 60,
+ 176,
+ 128,
+ 234,
+ 128,
+ 168,
+ 157,
+ 17,
+ 68,
+ 70,
+ 52,
+ 3,
+ 92,
+ 193,
+ 131,
+ 132
+ ],
+ "file-type" : "sources",
+ "path" : "/etc/apt/sources.list.d/proxmox.sources",
+ "repositories" : [
+ {
+ "Components" : [
+ "pve-no-subscription"
+ ],
+ "Enabled" : 0,
+ "FileType" : "sources",
+ "Options" : [
+ {
+ "Key" : "Signed-By",
+ "Values" : [
+ "/usr/share/keyrings/proxmox-archive-keyring.gpg"
+ ]
+ },
+ {
+ "Key" : "Enabled",
+ "Values" : [
+ "false"
+ ]
+ }
+ ],
+ "Suites" : [
+ "trixie"
+ ],
+ "Types" : [
+ "deb"
+ ],
+ "URIs" : [
+ "http://download.proxmox.com/debian/pve"
+ ]
+ },
+ {
+ "Components" : [
+ "pve-test"
+ ],
+ "Enabled" : 1,
+ "FileType" : "sources",
+ "Options" : [
+ {
+ "Key" : "Signed-By",
+ "Values" : [
+ "/usr/share/keyrings/proxmox-archive-keyring.gpg"
+ ]
+ }
+ ],
+ "Suites" : [
+ "trixie"
+ ],
+ "Types" : [
+ "deb"
+ ],
+ "URIs" : [
+ "http://download.proxmox.com/debian/pve"
+ ]
+ }
+ ]
+ },
+ {
+ "digest" : [
+ 240,
+ 120,
+ 248,
+ 18,
+ 110,
+ 2,
+ 197,
+ 189,
+ 38,
+ 29,
+ 38,
+ 0,
+ 102,
+ 69,
+ 77,
+ 164,
+ 121,
+ 40,
+ 26,
+ 112,
+ 179,
+ 41,
+ 253,
+ 179,
+ 100,
+ 65,
+ 49,
+ 152,
+ 186,
+ 96,
+ 165,
+ 40
+ ],
+ "file-type" : "list",
+ "path" : "/etc/apt/sources.list.d/ceph.list",
+ "repositories" : [
+ {
+ "Components" : [
+ "enterprise"
+ ],
+ "Enabled" : 0,
+ "FileType" : "list",
+ "Suites" : [
+ "bookworm"
+ ],
+ "Types" : [
+ "deb"
+ ],
+ "URIs" : [
+ "https://enterprise.proxmox.com/debian/ceph-quincy"
+ ]
+ },
+ {
+ "Components" : [
+ "no-subscription"
+ ],
+ "Enabled" : 0,
+ "FileType" : "list",
+ "Suites" : [
+ "bookworm"
+ ],
+ "Types" : [
+ "deb"
+ ],
+ "URIs" : [
+ "http://download.proxmox.com/debian/ceph-quincy"
+ ]
+ }
+ ]
+ },
+ {
+ "digest" : [
+ 206,
+ 114,
+ 195,
+ 192,
+ 184,
+ 195,
+ 73,
+ 240,
+ 227,
+ 21,
+ 111,
+ 157,
+ 215,
+ 226,
+ 7,
+ 187,
+ 189,
+ 165,
+ 36,
+ 130,
+ 22,
+ 142,
+ 192,
+ 201,
+ 249,
+ 11,
+ 112,
+ 159,
+ 247,
+ 190,
+ 35,
+ 155
+ ],
+ "file-type" : "sources",
+ "path" : "/etc/apt/sources.list.d/pve-enterprise.sources",
+ "repositories" : [
+ {
+ "Components" : [
+ "pve-9"
+ ],
+ "Enabled" : 0,
+ "FileType" : "sources",
+ "Options" : [
+ {
+ "Key" : "Signed-By",
+ "Values" : [
+ "/usr/share/keyrings/proxmox-archive-keyring.gpg"
+ ]
+ },
+ {
+ "Key" : "Enabled",
+ "Values" : [
+ "false"
+ ]
+ }
+ ],
+ "Suites" : [
+ "trixie"
+ ],
+ "Types" : [
+ "deb"
+ ],
+ "URIs" : [
+ "http://repo.proxmox.com/staging/pve"
+ ]
+ },
+ {
+ "Components" : [
+ "pve-enterprise"
+ ],
+ "Enabled" : 0,
+ "FileType" : "sources",
+ "Options" : [
+ {
+ "Key" : "Signed-By",
+ "Values" : [
+ "/usr/share/keyrings/proxmox-archive-keyring.gpg"
+ ]
+ },
+ {
+ "Key" : "Enabled",
+ "Values" : [
+ "false"
+ ]
+ }
+ ],
+ "Suites" : [
+ "trixie"
+ ],
+ "Types" : [
+ "deb"
+ ],
+ "URIs" : [
+ "https://enterprise.proxmox.com/debian/pve"
+ ]
+ }
+ ]
+ }
+ ],
+ "infos" : [
+ {
+ "index" : 0,
+ "kind" : "origin",
+ "message" : "Debian",
+ "path" : "/etc/apt/sources.list"
+ },
+ {
+ "index" : 1,
+ "kind" : "origin",
+ "message" : "Debian",
+ "path" : "/etc/apt/sources.list"
+ },
+ {
+ "index" : 2,
+ "kind" : "origin",
+ "message" : "Debian",
+ "path" : "/etc/apt/sources.list"
+ },
+ {
+ "index" : 0,
+ "kind" : "origin",
+ "message" : "Proxmox",
+ "path" : "/etc/apt/sources.list.d/proxmox.sources"
+ },
+ {
+ "index" : 1,
+ "kind" : "origin",
+ "message" : "Proxmox",
+ "path" : "/etc/apt/sources.list.d/proxmox.sources"
+ },
+ {
+ "index" : 0,
+ "kind" : "warning",
+ "message" : "old suite 'bookworm' configured!",
+ "path" : "/etc/apt/sources.list.d/ceph.list",
+ "property" : "Suites"
+ },
+ {
+ "index" : 1,
+ "kind" : "warning",
+ "message" : "old suite 'bookworm' configured!",
+ "path" : "/etc/apt/sources.list.d/ceph.list",
+ "property" : "Suites"
+ },
+ {
+ "index" : 0,
+ "kind" : "origin",
+ "message" : "Proxmox",
+ "path" : "/etc/apt/sources.list.d/ceph.list"
+ },
+ {
+ "index" : 1,
+ "kind" : "origin",
+ "message" : "Proxmox",
+ "path" : "/etc/apt/sources.list.d/ceph.list"
+ },
+ {
+ "index" : 0,
+ "kind" : "origin",
+ "message" : "Proxmox",
+ "path" : "/etc/apt/sources.list.d/pve-enterprise.sources"
+ },
+ {
+ "index" : 1,
+ "kind" : "origin",
+ "message" : "Proxmox",
+ "path" : "/etc/apt/sources.list.d/pve-enterprise.sources"
+ }
+ ],
+ "standard-repos" : [
+ {
+ "description" : "This is the default, stable, and recommended repository, available for all Proxmox subscription users.",
+ "handle" : "enterprise",
+ "name" : "Enterprise",
+ "status" : 0
+ },
+ {
+ "description" : "This is the recommended repository for testing and non-production use. Its packages are not as heavily tested and validated as the production ready enterprise repository. You don't need a subscription key to access this repository.",
+ "handle" : "no-subscription",
+ "name" : "No-Subscription",
+ "status" : 0
+ },
+ {
+ "description" : "This repository contains the latest packages and is primarily used for test labs and by developers to test new features.",
+ "handle" : "test",
+ "name" : "Test",
+ "status" : 1
+ },
+ {
+ "description" : "This repository holds the production-ready Proxmox Ceph Squid packages.",
+ "handle" : "ceph-squid-enterprise",
+ "name" : "Ceph Squid Enterprise"
+ },
+ {
+ "description" : "This repository holds the Proxmox Ceph Squid packages intended for non-production use.",
+ "handle" : "ceph-squid-no-subscription",
+ "name" : "Ceph Squid No-Subscription"
+ },
+ {
+ "description" : "This repository contains the Ceph Squid packages before they are moved to the main repository.",
+ "handle" : "ceph-squid-test",
+ "name" : "Ceph Squid Test"
+ }
+ ]
+}
diff --git a/server/tests/api_responses/pve/apt_update.json b/server/tests/api_responses/pve/apt_update.json
new file mode 100644
index 00000000..183acf94
--- /dev/null
+++ b/server/tests/api_responses/pve/apt_update.json
@@ -0,0 +1,638 @@
+[
+ {
+ "Arch" : "all",
+ "Description" : "This package contains the Proxmox VE Documentation files.",
+ "OldVersion" : "9.1.1",
+ "Origin" : "Proxmox",
+ "Package" : "pve-docs",
+ "Priority" : "optional",
+ "Section" : "doc",
+ "Title" : "Proxmox VE Documentation",
+ "Version" : "9.1.2"
+ },
+ {
+ "Arch" : "all",
+ "Description" : "This is a metapackage which will install the kernel image for the default Proxmox kernel series.",
+ "OldVersion" : "2.0.1",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-default-kernel",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Default Proxmox Kernel Image",
+ "Version" : "2.0.2"
+ },
+ {
+ "Arch" : "all",
+ "Description" : "The base framework providing widgets, models, and general utilities for the ExtJS based Web UIs of various Proxmox projects",
+ "OldVersion" : "5.1.2",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-widget-toolkit",
+ "Priority" : "optional",
+ "Section" : "web",
+ "Title" : "Core Widgets and ExtJS Helper Classes for Proxmox Web UIs",
+ "Version" : "5.1.5"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "This package contains the source for the Rust pve-rs crate, packaged by debcargo for use with cargo and dh-cargo.",
+ "OldVersion" : "0.11.3",
+ "Origin" : "Proxmox",
+ "Package" : "libpve-rs-perl",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "PVE parts which have been ported to Rust - Rust source code",
+ "Version" : "0.11.4"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "GnuPG is GNU's tool for secure communication and data storage. It can be used to encrypt data and to create digital signatures. It includes an advanced key management facility and is compliant with the proposed OpenPGP Internet standard as described in RFC4880. . This package contains /usr/bin/gpg itself, and is useful on its own only for public key operations (encryption, signature verification, listing OpenPGP certificates, etc). If you want full capabilities (including secret key operations, network access, etc), please install the \"gnupg\" package, which pulls in the full suite of tools.",
+ "OldVersion" : "2.4.7-21+b3",
+ "Origin" : "Debian",
+ "Package" : "gpg",
+ "Priority" : "optional",
+ "Section" : "utils",
+ "Title" : "GNU Privacy Guard -- minimalist public key operations",
+ "Version" : "2.4.7-21+deb13u1+b1"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "BusyBox combines tiny versions of many common UNIX utilities into a single small executable. It provides minimalist replacements for the most common utilities you would usually find on your desktop system (i.e., ls, cp, mv, mount, tar, etc.). The utilities in BusyBox generally have fewer options than their full-featured GNU cousins; however, the options that are included provide the expected functionality and behave very much like their GNU counterparts. . This package installs the BusyBox binary but does not install symlinks for any of the supported utilities. Some of the utilities can be used in the system by installing the busybox-syslogd, udhcpc or udhcpd packages.",
+ "OldVersion" : "1:1.37.0-6+b3",
+ "Origin" : "Debian",
+ "Package" : "busybox",
+ "Priority" : "optional",
+ "Section" : "utils",
+ "Title" : "Tiny utilities for small and embedded systems",
+ "Version" : "1:1.37.0-6+b5"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "sqv is a single-purpose CLI for verifying OpenPGP signatures, designed to be used from scripts. . OpenPGP users looking for the full range of OpenPGP functionality will usually prefer sq, the Sequoia PGP project's primary CLI, which provides a richer signature verification interface and other functionality like certificate management.",
+ "OldVersion" : "1.3.0-3",
+ "Origin" : "Debian",
+ "Package" : "sqv",
+ "Priority" : "optional",
+ "Section" : "utils",
+ "Title" : "OpenPGP signature verification program from Sequoia",
+ "Version" : "1.3.0-3+b2"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "Using KVM, one can run multiple virtual PCs, each running unmodified Linux or Windows images. Each virtual machine has private virtualized hardware: a network card, disk, graphics adapter, etc.",
+ "OldVersion" : "10.1.2-4",
+ "Origin" : "Proxmox",
+ "Package" : "pve-qemu-kvm",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Full virtualization on x86 hardware",
+ "Version" : "10.1.2-5"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "Libcap implements the user-space interfaces to the POSIX 1003.1e capabilities available in Linux kernels. These capabilities are a partitioning of the all powerful root privilege into a set of distinct privileges. . This package contains additional utilities.",
+ "OldVersion" : "1:2.75-10+b1",
+ "Origin" : "Debian",
+ "Package" : "libcap2-bin",
+ "Priority" : "optional",
+ "Section" : "utils",
+ "Title" : "POSIX 1003.1e capabilities (utilities)",
+ "Version" : "1:2.75-10+b3"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "GObject Introspection is a project for providing machine readable introspection data of the API of C libraries. This introspection data can be used in several different use cases, for example automatic code generation for bindings, API verification and documentation generation. . This package contains the introspection data for the GLib, GObject, GModule and Gio libraries, in the typelib format used to generate bindings for dynamic languages like JavaScript and Python.",
+ "OldVersion" : "2.84.4-3~deb13u1",
+ "Origin" : "Debian",
+ "Package" : "gir1.2-glib-2.0",
+ "Priority" : "optional",
+ "Section" : "introspection",
+ "Title" : "Introspection data for GLib, GObject, Gio and GModule",
+ "Version" : "2.84.4-3~deb13u2"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "debug symbols for qemu-server",
+ "OldVersion" : "9.1.1",
+ "Origin" : "Proxmox",
+ "Package" : "qemu-server-dbgsym",
+ "Priority" : "optional",
+ "Section" : "debug",
+ "Title" : "debug symbols for qemu-server",
+ "Version" : "9.1.3"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "The ext2, ext3 and ext4 file systems are successors of the original ext (\"extended\") file system. They are the main file system types used for hard disks on Debian and other Linux systems. . This package provides the ext2fs and e2p libraries, for userspace software that directly accesses extended file systems. Programs that use libext2fs include e2fsck, mke2fs, and tune2fs. Programs that use libe2p include dumpe2fs, chattr, and lsattr.",
+ "OldVersion" : "1.47.2-3+b3",
+ "Origin" : "Debian",
+ "Package" : "libext2fs2t64",
+ "Priority" : "optional",
+ "Section" : "libs",
+ "Title" : "ext2/ext3/ext4 file system libraries",
+ "Version" : "1.47.2-3+b7"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "HA Manager Proxmox VE.",
+ "OldVersion" : "5.0.8",
+ "Origin" : "Proxmox",
+ "Package" : "pve-ha-manager",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "Proxmox VE HA Manager",
+ "Version" : "5.1.0"
+ },
+ {
+ "Arch" : "all",
+ "Description" : "GnuPG is GNU's tool for secure communication and data storage. It can be used to encrypt data and to create digital signatures. It includes an advanced key management facility and is compliant with the proposed OpenPGP Internet standard as described in RFC4880. . This package contains the full suite of GnuPG tools for cryptographic communications and data storage.",
+ "OldVersion" : "2.4.7-21",
+ "Origin" : "Debian",
+ "Package" : "gnupg",
+ "Priority" : "optional",
+ "Section" : "utils",
+ "Title" : "GNU privacy guard - a free PGP replacement",
+ "Version" : "2.4.7-21+deb13u1"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "GnuPG is GNU's tool for secure communication and data storage. It can be used to encrypt data and to create digital signatures. It includes an advanced key management facility and is compliant with the proposed OpenPGP Internet standard as described in RFC4880. . This package provides the GnuPG server for the Web Key Service protocol. . A Web Key Service is a service that allows users to upload keys per mail to be verified over https as described in https://tools.ietf.org/html/draft-koch-openpgp-webkey-service . For more information see: https://wiki.gnupg.org/WKS",
+ "OldVersion" : "2.4.7-21+b3",
+ "Origin" : "Debian",
+ "Package" : "gpg-wks-server",
+ "Priority" : "optional",
+ "Section" : "utils",
+ "Title" : "GNU privacy guard - Web Key Service server",
+ "Version" : "2.4.7-21+deb13u1+b1"
+ },
+ {
+ "Arch" : "all",
+ "Description" : "This package provides the translations into all available languages.",
+ "OldVersion" : "3.6.5",
+ "Origin" : "Proxmox",
+ "Package" : "pve-yew-mobile-i18n",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "Internationalization support for Proxmox Virtual Environment (yew PWA)",
+ "Version" : "3.6.6"
+ },
+ {
+ "Arch" : "all",
+ "Description" : "GLib is a library containing many useful C routines for things such as trees, hashes, lists, and strings. It is a useful general-purpose C library used by projects such as GTK+, GIMP, and GNOME. . This package is needed for the runtime libraries to display messages in languages other than English.",
+ "OldVersion" : "2.84.4-3~deb13u1",
+ "Origin" : "Debian",
+ "Package" : "libglib2.0-data",
+ "Priority" : "optional",
+ "Section" : "libs",
+ "Title" : "Common files for GLib library",
+ "Version" : "2.84.4-3~deb13u2"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "GnuPG is GNU's tool for secure communication and data storage. It can be used to encrypt data and to create digital signatures. It includes an advanced key management facility and is compliant with the proposed OpenPGP Internet standard as described in RFC4880. . This package contains the agent program gpg-agent which handles all secret key material for OpenPGP and S/MIME use. The agent also provides a passphrase cache, which is used by pre-2.1 versions of GnuPG for OpenPGP operations. Without this package, trying to do secret-key operations with any part of the modern GnuPG suite will fail.",
+ "OldVersion" : "2.4.7-21+b3",
+ "Origin" : "Debian",
+ "Package" : "gpg-agent",
+ "Priority" : "optional",
+ "Section" : "utils",
+ "Title" : "GNU privacy guard - cryptographic agent",
+ "Version" : "2.4.7-21+deb13u1+b1"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "libcomerr is an attempt to present a common error-handling mechanism to manipulate the most common form of error code in a fashion that does not have the problems identified with mechanisms commonly in use.",
+ "OldVersion" : "1.47.2-3+b3",
+ "Origin" : "Debian",
+ "Package" : "libcom-err2",
+ "Priority" : "optional",
+ "Section" : "libs",
+ "Title" : "common error description library",
+ "Version" : "1.47.2-3+b7"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "NaCl (pronounced \"salt\") is a new easy-to-use high-speed software library for network communication, encryption, decryption, signatures, etc. . NaCl's goal is to provide all of the core operations needed to build higher-level cryptographic tools. . Sodium is a portable, cross-compilable, installable, packageable fork of NaCl, with a compatible API.",
+ "OldVersion" : "1.0.18-1+b2",
+ "Origin" : "Debian",
+ "Package" : "libsodium23",
+ "Priority" : "optional",
+ "Section" : "libs",
+ "Title" : "Network communication, cryptography and signaturing library",
+ "Version" : "1.0.18-1+deb13u1"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "Libcap implements the user-space interfaces to the POSIX 1003.1e capabilities available in Linux kernels. These capabilities are a partitioning of the all powerful root privilege into a set of distinct privileges. . This package contains the shared library.",
+ "OldVersion" : "1:2.75-10+b1",
+ "Origin" : "Debian",
+ "Package" : "libcap2",
+ "Priority" : "optional",
+ "Section" : "libs",
+ "Title" : "POSIX 1003.1e capabilities (library)",
+ "Version" : "1:2.75-10+b3"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "This package contains the Proxmox Backup single file restore client for restoring individual files and folders from both host/container and VM/block device backups. It includes a block device restore driver using QEMU.",
+ "OldVersion" : "4.1.0-1",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-backup-file-restore",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Proxmox Backup single file restore tools for pxar and block device backups",
+ "Version" : "4.1.1-1"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "Contains the standard libraries that are used by nearly all programs on the system. This package includes shared versions of the standard C library and the standard math library, as well as many others.",
+ "OldVersion" : "2.41-12",
+ "Origin" : "Debian",
+ "Package" : "libc6",
+ "Priority" : "optional",
+ "Section" : "libs",
+ "Title" : "GNU C Library: Shared libraries",
+ "Version" : "2.41-12+deb13u1"
+ },
+ {
+ "Arch" : "all",
+ "Description" : "Machine-readable data files, shared objects and programs used by the C library for localization (l10n) and internationalization (i18n) support. . This package contains tools to generate locale definitions from source files (included in this package). It allows you to customize which definitions actually get generated. This is a space-saver over how this package used to be, with all locales generated by default. This created a package that unpacked to an excess of 30 megs.",
+ "OldVersion" : "2.41-12",
+ "Origin" : "Debian",
+ "Package" : "locales",
+ "Priority" : "standard",
+ "Section" : "localization",
+ "Title" : "GNU C Library: National Language (locale) data [support]",
+ "Version" : "2.41-12+deb13u1"
+ },
+ {
+ "Arch" : "all",
+ "Description" : "This is a metapackage which will install the latest available proxmox kernel from the 6.14 series.",
+ "OldVersion" : "6.14.11-4",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-kernel-6.14",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Latest Proxmox Kernel Image",
+ "Version" : "6.14.11-5"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "This package contains the linux kernel and initial ramdisk used for booting . This package contains the kernel image signed by the Proxmox Secure Boot CA.",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-kernel-6.14.11-5-pve-signed",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Proxmox Kernel Image (signed)",
+ "Version" : "6.14.11-5"
+ },
+ {
+ "Arch" : "all",
+ "Description" : "This is a metapackage which will install the latest available proxmox kernel from the 6.17 series.",
+ "OldVersion" : "6.17.2-2",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-kernel-6.17",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Latest Proxmox Kernel Image",
+ "Version" : "6.17.4-2"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "This package contains the linux kernel and initial ramdisk used for booting . This package contains the kernel image signed by the Proxmox Secure Boot CA.",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-kernel-6.17.4-2-pve-signed",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Proxmox Kernel Image (signed)",
+ "Version" : "6.17.4-2"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "This package contains the Qemu Server tools used by Proxmox VE",
+ "OldVersion" : "9.1.1",
+ "Origin" : "Proxmox",
+ "Package" : "qemu-server",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Qemu Server Tools",
+ "Version" : "9.1.3"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "GnuPG is GNU's tool for secure communication and data storage. . gpgv is actually a stripped-down version of gpg which is only able to check signatures. It is somewhat smaller than the fully-blown gpg and uses a different (and simpler) way to check that the public keys used to make the signature are valid. There are no configuration files and only a few options are implemented.",
+ "OldVersion" : "2.4.7-21+b3",
+ "Origin" : "Debian",
+ "Package" : "gpgv",
+ "Priority" : "optional",
+ "Section" : "utils",
+ "Title" : "GNU privacy guard - signature verification tool",
+ "Version" : "2.4.7-21+deb13u1+b1"
+ },
+ {
+ "Arch" : "all",
+ "Description" : "This package contains the role based user management and access control function used by Proxmox VE.",
+ "OldVersion" : "9.0.4",
+ "Origin" : "Proxmox",
+ "Package" : "libpve-access-control",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "Proxmox VE access control library",
+ "Version" : "9.0.5"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "Bash is an sh-compatible command language interpreter that executes commands read from the standard input or from a file. Bash also incorporates useful features from the Korn and C shells (ksh and csh). . Bash is ultimately intended to be a conformant implementation of the IEEE POSIX Shell and Tools specification (IEEE Working Group 1003.2). . The Programmable Completion Code, by Ian Macdonald, is now found in the bash-completion package.",
+ "OldVersion" : "5.2.37-2+b5",
+ "Origin" : "Debian",
+ "Package" : "bash",
+ "Priority" : "required",
+ "Section" : "shells",
+ "Title" : "GNU Bourne Again SHell",
+ "Version" : "5.2.37-2+b7"
+ },
+ {
+ "Arch" : "all",
+ "Description" : "This package provides the translations into all available languages.",
+ "OldVersion" : "3.6.5",
+ "Origin" : "Proxmox",
+ "Package" : "pve-i18n",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "Internationalization support for Proxmox VE",
+ "Version" : "3.6.6"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "This package contains the basic filesystem hierarchy of a Debian system, and several important miscellaneous files, such as /etc/debian_version, /etc/host.conf, /etc/issue, /etc/motd, /etc/profile, and others, and the text of several common licenses in use on Debian systems.",
+ "OldVersion" : "13.8+deb13u2",
+ "Origin" : "Debian",
+ "Package" : "base-files",
+ "Priority" : "required",
+ "Section" : "admin",
+ "Title" : "Debian base system miscellaneous files",
+ "Version" : "13.8+deb13u3"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "GnuTLS is a portable library which implements the Transport Layer Security (TLS 1.0, 1.1, 1.2, 1.3) and Datagram Transport Layer Security (DTLS 1.0, 1.2) protocols. . GnuTLS features support for: - certificate path validation, as well as DANE and trust on first use. - the Online Certificate Status Protocol (OCSP). - public key methods, including RSA and Elliptic curves, as well as password and key authentication methods such as SRP and PSK protocols. - all the strong encryption algorithms, including AES and Camellia. - CPU-assisted cryptography with VIA padlock and AES-NI instruction sets. - HSMs and cryptographic tokens, via PKCS #11. . This package contains a commandline interface to the GNU TLS library, which can be used to set up secure connections from e.g. shell scripts, debugging connection issues or managing certificates. . Useful utilities include: - TLS termination: gnutls-cli, gnutls-serv - key and certificate management: certtool, ocsptool, p11tool - credential management: srptool, psktool",
+ "OldVersion" : "3.8.9-3",
+ "Origin" : "Debian",
+ "Package" : "gnutls-bin",
+ "Priority" : "optional",
+ "Section" : "net",
+ "Title" : "GNU TLS library - commandline utilities",
+ "Version" : "3.8.9-3+deb13u1"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "rsync is a fast and versatile file-copying tool which can copy locally and to/from a remote host. It offers many options to control its behavior, and its remote-update protocol can minimize network traffic to make transferring updates between machines fast and efficient. . It is widely used for backups and mirroring and as an improved copy command for everyday use. . This package provides both the rsync command line tool and optional daemon functionality.",
+ "OldVersion" : "3.4.1+ds1-5",
+ "Origin" : "Debian",
+ "Package" : "rsync",
+ "Priority" : "optional",
+ "Section" : "net",
+ "Title" : "fast, versatile, remote (and local) file-copying tool",
+ "Version" : "3.4.1+ds1-5+deb13u1"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "GnuPG is GNU's tool for secure communication and data storage. It can be used to encrypt data and to create digital signatures. It includes an advanced key management facility and is compliant with the proposed OpenPGP Internet standard as described in RFC4880. . This package contains the gpgsm program. gpgsm is a tool to provide digital encryption and signing services on X.509 certificates and the CMS protocol. gpgsm includes complete certificate management.",
+ "OldVersion" : "2.4.7-21+b3",
+ "Origin" : "Debian",
+ "Package" : "gpgsm",
+ "Priority" : "optional",
+ "Section" : "utils",
+ "Title" : "GNU privacy guard - S/MIME version",
+ "Version" : "2.4.7-21+deb13u1+b1"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "libunbound performs and validates DNS lookups; it can be used to convert hostnames to IP addresses and back and obtain other information from the DNS. Cryptographic validation of results is performed with DNSSEC.",
+ "OldVersion" : "1.22.0-2",
+ "Origin" : "Debian",
+ "Package" : "libunbound8",
+ "Priority" : "optional",
+ "Section" : "libs",
+ "Title" : "library implementing DNS resolution and validation",
+ "Version" : "1.22.0-2+deb13u1"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "This package contains the Proxmox Backup client, which provides a simple command line tool to create and restore backups.",
+ "OldVersion" : "4.1.0-1",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-backup-client",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Proxmox Backup Client tools",
+ "Version" : "4.1.1-1"
+ },
+ {
+ "Arch" : "all",
+ "Description" : "This package contains the API endpoints for the Software Defined Network of Proxmox VE.",
+ "OldVersion" : "1.2.3",
+ "Origin" : "Proxmox",
+ "Package" : "libpve-network-api-perl",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "API endpoints for Proxmox VE's SDN stack",
+ "Version" : "1.2.4"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "GnuTLS is a portable library which implements the Transport Layer Security (TLS 1.0, 1.1, 1.2, 1.3) and Datagram Transport Layer Security (DTLS 1.0, 1.2) protocols. . GnuTLS features support for: - certificate path validation, as well as DANE and trust on first use. - the Online Certificate Status Protocol (OCSP). - public key methods, including RSA and Elliptic curves, as well as password and key authentication methods such as SRP and PSK protocols. - all the strong encryption algorithms, including AES and Camellia. - CPU-assisted cryptography with VIA padlock and AES-NI instruction sets. - HSMs and cryptographic tokens, via PKCS #11. . This package contains the runtime library for DANE (DNS-based Authentication of Named Entities) support.",
+ "OldVersion" : "3.8.9-3",
+ "Origin" : "Debian",
+ "Package" : "libgnutls-dane0t64",
+ "Priority" : "optional",
+ "Section" : "libs",
+ "Title" : "GNU TLS library - DANE security support",
+ "Version" : "3.8.9-3+deb13u1"
+ },
+ {
+ "Arch" : "all",
+ "Description" : "This package contains the Proxmox Virtual Environment management tools.",
+ "OldVersion" : "9.1.2",
+ "Origin" : "Proxmox",
+ "Package" : "pve-manager",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Proxmox Virtual Environment Management Tools",
+ "Version" : "9.1.4"
+ },
+ {
+ "Arch" : "all",
+ "Description" : "This package contains the base library used by other Proxmox VE components.",
+ "OldVersion" : "9.1.0",
+ "Origin" : "Proxmox",
+ "Package" : "libpve-common-perl",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "Proxmox VE base library",
+ "Version" : "9.1.4"
+ },
+ {
+ "Arch" : "all",
+ "Description" : "This package contains the perl side of the Software Defined Network implementation for Proxmox VE.",
+ "OldVersion" : "1.2.3",
+ "Origin" : "Proxmox",
+ "Package" : "libpve-network-perl",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "Proxmox VE's SDN (Software Defined Network) stack",
+ "Version" : "1.2.4"
+ },
+ {
+ "Arch" : "all",
+ "Description" : "This package contains the translation files for the GNU C library and utility programs.",
+ "OldVersion" : "2.41-12",
+ "Origin" : "Debian",
+ "Package" : "libc-l10n",
+ "Priority" : "standard",
+ "Section" : "localization",
+ "Title" : "GNU C Library: localization files",
+ "Version" : "2.41-12+deb13u1"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "This package contains utility programs related to the GNU C Library. . * getconf: query system configuration variables * getent: get entries from administrative databases * iconv, iconvconfig: convert between character encodings * ldd, ldconfig: print/configure shared library dependencies * locale, localedef: show/generate locale definitions * tzselect, zdump, zic: select/dump/compile time zones",
+ "OldVersion" : "2.41-12",
+ "Origin" : "Debian",
+ "Package" : "libc-bin",
+ "Priority" : "required",
+ "Section" : "libs",
+ "Title" : "GNU C Library: Binaries",
+ "Version" : "2.41-12+deb13u1"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "c-ares is a C library that performs DNS requests and name resolution asynchronously. . It is a fork of the library named \"ares\", with additional features: * IPv6 support; * extended cross-platform portability; * 64-bit clean sources. . This package provides the shared libraries.",
+ "OldVersion" : "1.34.5-1",
+ "Origin" : "Debian",
+ "Package" : "libcares2",
+ "Priority" : "optional",
+ "Section" : "libs",
+ "Title" : "asynchronous name resolver",
+ "Version" : "1.34.5-1+deb13u1"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "GnuTLS is a portable library which implements the Transport Layer Security (TLS 1.0, 1.1, 1.2, 1.3) and Datagram Transport Layer Security (DTLS 1.0, 1.2) protocols. . GnuTLS features support for: - certificate path validation, as well as DANE and trust on first use. - the Online Certificate Status Protocol (OCSP). - public key methods, including RSA and Elliptic curves, as well as password and key authentication methods such as SRP and PSK protocols. - all the strong encryption algorithms, including AES and Camellia. - CPU-assisted cryptography with VIA padlock and AES-NI instruction sets. - HSMs and cryptographic tokens, via PKCS #11. . This package contains the main runtime library.",
+ "OldVersion" : "3.8.9-3",
+ "Origin" : "Debian",
+ "Package" : "libgnutls30t64",
+ "Priority" : "optional",
+ "Section" : "libs",
+ "Title" : "GNU TLS library - main runtime library",
+ "Version" : "3.8.9-3+deb13u1"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "dirmngr is a server for managing and downloading OpenPGP and X.509 certificates, as well as updates and status signals related to those certificates. For OpenPGP, this means pulling from the public HKP/HKPS keyservers, or from LDAP servers. For X.509 this includes Certificate Revocation Lists (CRLs) and Online Certificate Status Protocol updates (OCSP). It is capable of using Tor for network access. . dirmngr is used for network access by gpg, gpgsm, and dirmngr-client, among other tools. Unless this package is installed, the parts of the GnuPG suite that try to interact with the network will fail.",
+ "OldVersion" : "2.4.7-21+b3",
+ "Origin" : "Debian",
+ "Package" : "dirmngr",
+ "Priority" : "optional",
+ "Section" : "utils",
+ "Title" : "GNU privacy guard - network certificate management service",
+ "Version" : "2.4.7-21+deb13u1+b1"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "GnuPG is GNU's tool for secure communication and data storage. . This package contains several useful utilities for manipulating OpenPGP data and other related cryptographic elements. It includes: . * addgnupghome -- create .gnupg home directories * applygnupgdefaults -- run gpgconf --apply-defaults for all users * gpgparsemail -- parse an e-mail message into annotated format * gpgsplit -- split a sequence of OpenPGP packets into files * gpgtar -- encrypt or sign files in an archive * kbxutil -- list, export, import Keybox data * lspgpot -- convert PGP ownertrust values to GnuPG * migrate-pubring-from-classic-gpg -- use only \"modern\" formats * symcryptrun -- use simple symmetric encryption tool in GnuPG framework * watchgnupg -- watch socket-based logs",
+ "OldVersion" : "2.4.7-21+b3",
+ "Origin" : "Debian",
+ "Package" : "gnupg-utils",
+ "Priority" : "extra",
+ "Section" : "utils",
+ "Title" : "GNU privacy guard - utility programs",
+ "Version" : "2.4.7-21+deb13u1+b1"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "The logsave program will execute cmd_prog with the specified argument(s), and save a copy of its output to logfile. If the containing directory for logfile does not exist, logsave will accumulate the output in memory until it can be written out. A copy of the output will also be written to standard output.",
+ "OldVersion" : "1.47.2-3+b3",
+ "Origin" : "Debian",
+ "Package" : "logsave",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "save the output of a command in a log file",
+ "Version" : "1.47.2-3+b7"
+ },
+ {
+ "Arch" : "all",
+ "Description" : "GnuPG is GNU's tool for secure communication and data storage. It can be used to encrypt data and to create digital signatures. It includes an advanced key management facility and is compliant with the proposed OpenPGP Internet standard as described in RFC 4880. . This package contains the translation files for the use of GnuPG in non-English locales.",
+ "OldVersion" : "2.4.7-21",
+ "Origin" : "Debian",
+ "Package" : "gnupg-l10n",
+ "Priority" : "optional",
+ "Section" : "localization",
+ "Title" : "GNU privacy guard - localization files",
+ "Version" : "2.4.7-21+deb13u1"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "GnuPG is GNU's tool for secure communication and data storage. It can be used to encrypt data and to create digital signatures. It includes an advanced key management facility and is compliant with the proposed OpenPGP Internet standard as described in RFC4880. . This package provides the GnuPG client for the Web Key Service protocol. . A Web Key Service is a service that allows users to upload keys per mail to be verified over https as described in https://tools.ietf.org/html/draft-koch-openpgp-webkey-service . For more information see: https://wiki.gnupg.org/WKS",
+ "OldVersion" : "2.4.7-21+b3",
+ "Origin" : "Debian",
+ "Package" : "gpg-wks-client",
+ "Priority" : "optional",
+ "Section" : "utils",
+ "Title" : "GNU privacy guard - Web Key Service client",
+ "Version" : "2.4.7-21+deb13u1+b1"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "libpng is a library implementing an interface for reading and writing PNG (Portable Network Graphics) format files. . This package contains the runtime library files needed to run software using libpng.",
+ "OldVersion" : "1.6.48-1",
+ "Origin" : "Debian",
+ "Package" : "libpng16-16t64",
+ "Priority" : "optional",
+ "Section" : "libs",
+ "Title" : "PNG library - runtime (version 1.6)",
+ "Version" : "1.6.48-1+deb13u1"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "GnuPG is GNU's tool for secure communication and data storage. . This package contains core utilities used by different tools in the suite offered by GnuPG. It can be used to programmatically edit config files for tools in the GnuPG suite, to launch or terminate per-user daemons (if installed), etc.",
+ "OldVersion" : "2.4.7-21+b3",
+ "Origin" : "Debian",
+ "Package" : "gpgconf",
+ "Priority" : "optional",
+ "Section" : "utils",
+ "Title" : "GNU privacy guard - core configuration utilities",
+ "Version" : "2.4.7-21+deb13u1+b1"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "libss provides a simple command-line interface parser which will accept input from the user, parse the command into an argv argument vector, and then dispatch it to a handler function. . It was originally inspired by the Multics SubSystem library.",
+ "OldVersion" : "1.47.2-3+b3",
+ "Origin" : "Debian",
+ "Package" : "libss2",
+ "Priority" : "optional",
+ "Section" : "libs",
+ "Title" : "command-line interface parsing library",
+ "Version" : "1.47.2-3+b7"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "GLib is a library containing many useful C routines for things such as trees, hashes, lists, and strings. It is a useful general-purpose C library used by projects such as GTK+, GIMP, and GNOME. . This package contains the shared libraries.",
+ "OldVersion" : "2.84.4-3~deb13u1",
+ "Origin" : "Debian",
+ "Package" : "libglib2.0-0t64",
+ "Priority" : "optional",
+ "Section" : "libs",
+ "Title" : "GLib library of C routines",
+ "Version" : "2.84.4-3~deb13u2"
+ },
+ {
+ "Arch" : "amd64",
+ "Description" : "The ext2, ext3 and ext4 file systems are successors of the original ext (\"extended\") file system. They are the main file system types used for hard disks on Debian and other Linux systems. . This package contains programs for creating, checking, and maintaining ext2/3/4-based file systems. It also includes the \"badblocks\" program, which can be used to scan for bad blocks on a disk or other storage device.",
+ "OldVersion" : "1.47.2-3+b3",
+ "Origin" : "Debian",
+ "Package" : "e2fsprogs",
+ "Priority" : "important",
+ "Section" : "admin",
+ "Title" : "ext2/ext3/ext4 file system utilities",
+ "Version" : "1.47.2-3+b7"
+ }
+]
diff --git a/server/tests/api_responses/pve/apt_versions.json b/server/tests/api_responses/pve/apt_versions.json
new file mode 100644
index 00000000..005a62dc
--- /dev/null
+++ b/server/tests/api_responses/pve/apt_versions.json
@@ -0,0 +1,736 @@
+[
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "The Proxmox Virtual Environment is an easy to use Open Source virtualization platform for running Virtual Appliances and Virtual Machines. This is a meta package which will install everything needed.",
+ "OldVersion" : "9.1.0",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-ve",
+ "Priority" : "optional",
+ "RunningKernel" : "6.17.2-2-pve",
+ "Section" : "admin",
+ "Title" : "Proxmox Virtual Environment",
+ "Version" : "9.1.0"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the Proxmox Virtual Environment management tools.",
+ "ManagerVersion" : "9.1.2/9d436f37a0ac4172",
+ "OldVersion" : "9.1.2",
+ "Origin" : "Proxmox",
+ "Package" : "pve-manager",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Proxmox Virtual Environment Management Tools",
+ "Version" : "9.1.4"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "This package includes kernel-hooks for marking certain kernels as NeverAutoRemove and helpers for systemd-boot",
+ "OldVersion" : "9.0.4",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-kernel-helper",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Function for various kernel maintenance tasks.",
+ "Version" : "9.0.4"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the linux kernel and initial ramdisk used for booting . This package contains the kernel image signed by the Proxmox Secure Boot CA.",
+ "OldVersion" : "6.17.2-2",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-kernel-6.17.2-2-pve-signed",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Proxmox Kernel Image (signed)",
+ "Version" : "6.17.2-2"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "This is a metapackage which will install the latest available proxmox kernel from the 6.17 series.",
+ "OldVersion" : "6.17.2-2",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-kernel-6.17",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Latest Proxmox Kernel Image",
+ "Version" : "6.17.9-1"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the linux kernel and initial ramdisk used for booting . This package contains the kernel image signed by the Proxmox Secure Boot CA.",
+ "OldVersion" : "6.14.11-4",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-kernel-6.14.11-4-pve-signed",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Proxmox Kernel Image (signed)",
+ "Version" : "6.14.11-4"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "This is a metapackage which will install the latest available proxmox kernel from the 6.14 series.",
+ "OldVersion" : "6.14.11-4",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-kernel-6.14",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Latest Proxmox Kernel Image",
+ "Version" : "6.14.11-5"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the linux kernel and initial ramdisk used for booting . This package contains the kernel image signed by the Proxmox Secure Boot CA.",
+ "OldVersion" : "6.8.12-13",
+ "Origin" : "unknown",
+ "Package" : "proxmox-kernel-6.8.12-13-pve-signed",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Proxmox Kernel Image (signed)",
+ "Version" : "6.8.12-13"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "This is a metapackage which will install the latest available proxmox kernel from the 6.8 series.",
+ "OldVersion" : "6.8.12-13",
+ "Origin" : "unknown",
+ "Package" : "proxmox-kernel-6.8",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Latest Proxmox Kernel Image",
+ "Version" : "6.8.12-13"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the linux kernel and initial ramdisk used for booting . This package contains the kernel image signed by the Proxmox Secure Boot CA.",
+ "OldVersion" : "6.8.12-4",
+ "Origin" : "unknown",
+ "Package" : "proxmox-kernel-6.8.12-4-pve-signed",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Proxmox Kernel Image (signed)",
+ "Version" : "6.8.12-4"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "Ceph is a massively scalable, open-source, distributed storage system that runs on commodity hardware and delivers object, block and file system storage. This is a FUSE-based client that allows one to mount a Ceph file system without root privileges. . Because the FUSE-based client has certain inherent performance limitations, it is recommended that the native Linux kernel client be used if possible. If it is not practical to load a kernel module (insufficient privileges, older kernel, etc.), then the FUSE client will do.",
+ "OldVersion" : "19.2.3-pve1",
+ "Origin" : "Proxmox",
+ "Package" : "ceph-fuse",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "FUSE-based client for the Ceph distributed file system",
+ "Version" : "19.2.3-pve1"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "The Corosync Cluster Engine is a Group Communication System with additional features for implementing high availability within applications. The project provides four C Application Programming Interface features: . * A closed process group communication model with virtual synchrony guarantees for creating replicated state machines. * A simple availability manager that restarts the application process when it has failed. * A configuration and statistics in-memory database that provide the ability to set, retrieve, and receive change notifications of information. * A quorum system that notifies applications when quorum is achieved or lost. . This package contains the Corosync daemon and some administration tools.",
+ "OldVersion" : "3.1.9-pve2",
+ "Origin" : "Proxmox",
+ "Package" : "corosync",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "cluster engine daemon and utilities",
+ "Version" : "3.1.9-pve2"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "criu contains the utilities to checkpoint and restore processes in userspace. . It can freeze a running container (or an individual application) and checkpoint its state to disk. The data saved can be used to restore the application and run it exactly as it was during the time of the freeze. Using this functionality, application or container live migration, snapshots, remote debugging, and many other things are now possible. . This package provides the criu and compel binaries, comprising the main functionality of criu. . Note that 'criu-ns' and 'crit' scripts come with the python3-pycriu package.",
+ "OldVersion" : "4.1.1-1",
+ "Origin" : "Debian",
+ "Package" : "criu",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "checkpoint and restore in userspace",
+ "Version" : "4.1.1-1"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "The FRRouting suite uses a small Python tool to provide configuration reload functionality, particularly useful when the interactive configuration shell is not used. . Without this package installed, \"reload\" (as a systemd or init script invocation) will not work for the FRR daemons.",
+ "OldVersion" : "10.4.1-1+pve1",
+ "Origin" : "Proxmox",
+ "Package" : "frr-pythontools",
+ "Priority" : "optional",
+ "Section" : "net",
+ "Title" : "FRRouting suite - Python tools",
+ "Version" : "10.4.1-1+pve1"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "ifupdown2 is ifupdown re-written in Python. It replaces ifupdown and provides the same user interface as ifupdown for network interface configuration. Like ifupdown, ifupdown2 is a high level tool to configure (or, respectively deconfigure) network interfaces based on interface definitions in /etc/network/interfaces. It is capable of detecting network interface dependencies and comes with several new features which are available as new command options to ifup/ifdown/ifquery commands. It also comes with a new command ifreload to reload interface configuration with minimum disruption. Most commands are also capable of input and output in JSON format. It is backward compatible with ifupdown /etc/network/interfaces format and supports newer simplified format. It also supports interface templates with python-mako for large scale interface deployments. See /usr/share/doc/ifupdown2/README.rst for details about ifupdown2. Examples are available under /usr/share/doc/ifupdown2/examples.",
+ "OldVersion" : "3.3.0-1+pmx11",
+ "Origin" : "Proxmox",
+ "Package" : "ifupdown2",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Network Interface Management tool similar to ifupdown",
+ "Version" : "3.3.0-1+pmx11"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the KSM tuning daemon which controls whether ksm should ksm search duplicated pages.",
+ "OldVersion" : "1.5-1",
+ "Origin" : "Proxmox",
+ "Package" : "ksm-control-daemon",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Kernel Samepage Merging (KSM) Tuning Daemon",
+ "Version" : "1.5-1"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "Ext JS is a cross-browser JavaScript library for building rich internet applications.",
+ "OldVersion" : "7.0.0-5",
+ "Origin" : "Proxmox",
+ "Package" : "libjs-extjs",
+ "Priority" : "optional",
+ "Section" : "web",
+ "Title" : "cross-browser JavaScript library",
+ "Version" : "7.0.0-5"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "Used in perl-based Proxmox project as common interface for DNS and HTTP ACME challenges.",
+ "OldVersion" : "1.7.0",
+ "Origin" : "Proxmox",
+ "Package" : "libproxmox-acme-perl",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Proxmox ACME integration perl library",
+ "Version" : "1.7.0"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "This library contains the library to access the Proxmox Backup server from within QEMU.",
+ "OldVersion" : "2.0.1",
+ "Origin" : "Proxmox",
+ "Package" : "libproxmox-backup-qemu0",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Proxmox Backup Server client library for QEMU",
+ "Version" : "2.0.1"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "Contains the perl side of modules provided by the libraries of both libpve-rs-perl and libpmg-rs-perl, loading whichever is available.",
+ "OldVersion" : "0.4.1",
+ "Origin" : "Proxmox",
+ "Package" : "libproxmox-rs-perl",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "PVE/PMG common perl parts for Rust perlmod bindings",
+ "Version" : "0.4.1"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the role based user management and access control function used by Proxmox VE.",
+ "OldVersion" : "9.0.4",
+ "Origin" : "Proxmox",
+ "Package" : "libpve-access-control",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "Proxmox VE access control library",
+ "Version" : "9.0.5"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "This implements an API client for Proxmox VE in perl.",
+ "OldVersion" : "3.4.2",
+ "Origin" : "Proxmox",
+ "Package" : "libpve-apiclient-perl",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "Proxmox VE API client library",
+ "Version" : "3.4.2"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the API2 endpoints and CLI binary 'pvecm'.",
+ "OldVersion" : "9.0.7",
+ "Origin" : "Proxmox",
+ "Package" : "libpve-cluster-api-perl",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Proxmox Virtual Environment cluster Perl API modules.",
+ "Version" : "9.0.7"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains various cluster-related perl modules.",
+ "OldVersion" : "9.0.7",
+ "Origin" : "Proxmox",
+ "Package" : "libpve-cluster-perl",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Proxmox Virtual Environment cluster Perl modules.",
+ "Version" : "9.0.7"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the base library used by other Proxmox VE components.",
+ "OldVersion" : "9.1.0",
+ "Origin" : "Proxmox",
+ "Package" : "libpve-common-perl",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "Proxmox VE base library",
+ "Version" : "9.1.7"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains a common code base for Proxmox VE guests. It is mainly used by pve-container and qemu-server.",
+ "OldVersion" : "6.0.2",
+ "Origin" : "Proxmox",
+ "Package" : "libpve-guest-common-perl",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "Proxmox VE common guest-related modules",
+ "Version" : "6.0.2"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "This package is used as base to implement the REST API in all perl based Proxmox projects.",
+ "OldVersion" : "6.0.5",
+ "Origin" : "Proxmox",
+ "Package" : "libpve-http-server-perl",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "Proxmox Asynchrounous HTTP Server Implementation",
+ "Version" : "6.0.5"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the perl side of the Software Defined Network implementation for Proxmox VE.",
+ "OldVersion" : "1.2.3",
+ "Origin" : "Proxmox",
+ "Package" : "libpve-network-perl",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "Proxmox VE's SDN (Software Defined Network) stack",
+ "Version" : "1.2.4"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the source for the Rust pve-rs crate, packaged by debcargo for use with cargo and dh-cargo.",
+ "OldVersion" : "0.11.3",
+ "Origin" : "Proxmox",
+ "Package" : "libpve-rs-perl",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "PVE parts which have been ported to Rust - Rust source code",
+ "Version" : "0.11.4"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the storage management library used by Proxmox VE.",
+ "OldVersion" : "9.1.0",
+ "Origin" : "Proxmox",
+ "Package" : "libpve-storage-perl",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "Proxmox VE storage management library",
+ "Version" : "9.1.0"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "The Simple Protocol for Independent Computing Environments (SPICE) is a remote display system built for virtual environments which allows you to view a computing 'desktop' environment not only on the machine where it is running, but from anywhere on the Internet and from a wide variety of machine architectures. . This package contains the run-time libraries for any application that wishes to be a SPICE server.",
+ "OldVersion" : "0.15.2-1+b1",
+ "Origin" : "Debian",
+ "Package" : "libspice-server1",
+ "Priority" : "optional",
+ "Section" : "libs",
+ "Title" : "Implements the server side of the SPICE protocol",
+ "Version" : "0.15.2-1+b1"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "This is LVM2, the rewrite of The Linux Logical Volume Manager. LVM supports enterprise level volume management of disk and disk subsystems by grouping arbitrary disks into volume groups. The total capacity of volume groups can be allocated to logical volumes, which are accessed as regular block devices.",
+ "OldVersion" : "2.03.31-2+pmx1",
+ "Origin" : "Proxmox",
+ "Package" : "lvm2",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Linux Logical Volume Manager",
+ "Version" : "2.03.31-2+pmx1"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "Containers provides resource management through control groups and resource isolation through namespaces. The linux containers, lxc, aims to use these new functionalities to provide an userspace container object which provides full resource isolation and resource control for an applications or a system.",
+ "OldVersion" : "6.0.5-3",
+ "Origin" : "Proxmox",
+ "Package" : "lxc-pve",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Linux containers userspace tools",
+ "Version" : "6.0.5-3"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "LXCFS is a simple userspace filesystem designed to workaround some current limitations of the Linux kernel. The main driver for this work was the need to run systemd based containers as a regular unprivileged user while still allowing systemd inside the container to interact with cgroups.",
+ "OldVersion" : "6.0.4-pve1",
+ "Origin" : "Proxmox",
+ "Package" : "lxcfs",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "LXC userspace filesystem",
+ "Version" : "6.0.4-pve1"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "VNC client using HTML5 (WebSockets, Canvas). This packet is use by Proxmox VE to provide HTML VM console.",
+ "OldVersion" : "1.6.0-3",
+ "Origin" : "Proxmox",
+ "Package" : "novnc-pve",
+ "Priority" : "optional",
+ "Section" : "web",
+ "Title" : "HTML5 VNC client",
+ "Version" : "1.6.0-3"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the Proxmox Backup client, which provides a simple command line tool to create and restore backups.",
+ "OldVersion" : "4.1.0-1",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-backup-client",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Proxmox Backup Client tools",
+ "Version" : "4.1.1-1"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the Proxmox Backup single file restore client for restoring individual files and folders from both host/container and VM/block device backups. It includes a block device restore driver using QEMU.",
+ "OldVersion" : "4.1.0-1",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-backup-file-restore",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Proxmox Backup single file restore tools for pxar and block device backups",
+ "Version" : "4.1.1-1"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "Preconfigured images used as base for single file restore of Proxmox Backup Server snapshots. Not really useful on their own, so best used together with the proxmox-backup-file-restore package, which provide the actual tools.",
+ "OldVersion" : "1.0.0",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-backup-restore-image",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Kernel/initramfs images for Proxmox Backup single-file restore.",
+ "Version" : "1.0.0"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains a nftables-based implementation of the Proxmox VE Firewall",
+ "OldVersion" : "1.2.1",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-firewall",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Proxmox's nftables-based firewall written in rust",
+ "Version" : "1.2.1"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "This package includes kernel-hooks for marking certain kernels as NeverAutoRemove and helpers for systemd-boot",
+ "OldVersion" : "9.0.4",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-kernel-helper",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Function for various kernel maintenance tasks.",
+ "Version" : "9.0.4"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the Proxmox mail forward helper. It forwards mails to the address(es) of the root@pam user in Proxmox Backup Server and Proxmox VE.",
+ "OldVersion" : "1.0.2",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-mail-forward",
+ "Priority" : "optional",
+ "Section" : "rust",
+ "Title" : "Proxmox mail forward helper",
+ "Version" : "1.0.2"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "A minimal application to read the last X lines of the systemd journal or the last X lines before a cursor.",
+ "OldVersion" : "1.6",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-mini-journalreader",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Minimal systemd Journal Reader",
+ "Version" : "1.6"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the proxmox-offline-mirror-helper binary for managing Proxmox offline APT repositories and subscription keys on Proxmox offline systems.",
+ "OldVersion" : "0.7.3",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-offline-mirror-helper",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Proxmox offline repository mirror and subscription key manager helper",
+ "Version" : "0.7.3"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "The base framework providing widgets, models, and general utilities for the ExtJS based Web UIs of various Proxmox projects",
+ "OldVersion" : "5.1.2",
+ "Origin" : "Proxmox",
+ "Package" : "proxmox-widget-toolkit",
+ "Priority" : "optional",
+ "Section" : "web",
+ "Title" : "Core Widgets and ExtJS Helper Classes for Proxmox Web UIs",
+ "Version" : "5.1.5"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "This FUSE filesystem is using corosync and sqlite3 to provide a cluster-wide, consistent view of config and other files.",
+ "OldVersion" : "9.0.7",
+ "Origin" : "Proxmox",
+ "Package" : "pve-cluster",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "\"pmxcfs\" distributed cluster filesystem for Proxmox Virtual Environment.",
+ "Version" : "9.0.7"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "Tool to manage Linux Containers on Proxmox VE.",
+ "OldVersion" : "6.0.18",
+ "Origin" : "Proxmox",
+ "Package" : "pve-container",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "Proxmox VE Container management tool",
+ "Version" : "6.0.18"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the Proxmox VE Documentation files.",
+ "OldVersion" : "9.1.1",
+ "Origin" : "Proxmox",
+ "Package" : "pve-docs",
+ "Priority" : "optional",
+ "Section" : "doc",
+ "Title" : "Proxmox VE Documentation",
+ "Version" : "9.1.2"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "Open Virtual Machine Firmware is a build of EDK II for 64-bit, 32-bit x86 virtual machines. It includes full support for UEFI, including Secure Boot, allowing use of UEFI in place of a traditional BIOS in your VM. Meta package depending on OVMF and Legacy OVMF images to ease the upgrade transition.",
+ "OldVersion" : "4.2025.05-2",
+ "Origin" : "Proxmox",
+ "Package" : "pve-edk2-firmware",
+ "Priority" : "optional",
+ "Section" : "misc",
+ "Title" : "edk2 based UEFI firmware modules for virtual machines",
+ "Version" : "4.2025.05-2"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "Provides a FUSE file system to access files on ESXi hosts and a python utility to query the list of VMs and datastores.",
+ "OldVersion" : "1.0.1",
+ "Origin" : "Proxmox",
+ "Package" : "pve-esxi-import-tools",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Tools to allow importing VMs from ESXi hosts",
+ "Version" : "1.0.1"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the Proxmox VE Firewall.",
+ "OldVersion" : "6.0.4",
+ "Origin" : "Proxmox",
+ "Package" : "pve-firewall",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Proxmox VE Firewall",
+ "Version" : "6.0.4"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the binary firmware for various modules used in the pve-kernel.",
+ "OldVersion" : "3.17-2",
+ "Origin" : "Proxmox",
+ "Package" : "pve-firmware",
+ "Priority" : "optional",
+ "Section" : "misc",
+ "Title" : "Binary firmware code for the pve-kernel",
+ "Version" : "3.17-2"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "HA Manager Proxmox VE.",
+ "OldVersion" : "5.0.8",
+ "Origin" : "Proxmox",
+ "Package" : "pve-ha-manager",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "Proxmox VE HA Manager",
+ "Version" : "5.1.0"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "This package provides the translations into all available languages.",
+ "OldVersion" : "3.6.5",
+ "Origin" : "Proxmox",
+ "Package" : "pve-i18n",
+ "Priority" : "optional",
+ "Section" : "perl",
+ "Title" : "Internationalization support for Proxmox VE",
+ "Version" : "3.6.6"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "Using KVM, one can run multiple virtual PCs, each running unmodified Linux or Windows images. Each virtual machine has private virtualized hardware: a network card, disk, graphics adapter, etc.",
+ "OldVersion" : "10.1.2-4",
+ "Origin" : "Proxmox",
+ "Package" : "pve-qemu-kvm",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Full virtualization on x86 hardware",
+ "Version" : "10.1.2-5"
+ },
+ {
+ "Arch" : "all",
+ "CurrentState" : "Installed",
+ "Description" : "Provides the xterm.js frontend for the terminal feature in Proxmox projects' web UI's, like for host administration or Proxmox VE containers shells.",
+ "OldVersion" : "5.5.0-3",
+ "Origin" : "Proxmox",
+ "Package" : "pve-xtermjs",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "HTML/TypeScript based fully-featured terminal for Proxmox projects",
+ "Version" : "5.5.0-3"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "This package contains the Qemu Server tools used by Proxmox VE",
+ "OldVersion" : "9.1.1",
+ "Origin" : "Proxmox",
+ "Package" : "qemu-server",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "Qemu Server Tools",
+ "Version" : "9.1.4"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "The smartmontools package contains two utility programs (smartctl and smartd) to control and monitor storage systems using the Self-Monitoring, Analysis and Reporting Technology System (S.M.A.R.T.) built into most modern ATA and SCSI hard disks. It is derived from the smartsuite package, and includes support for ATA/ATAPI-5 disks. It should run on any modern Linux system.",
+ "OldVersion" : "7.4-pve1",
+ "Origin" : "Proxmox",
+ "Package" : "smartmontools",
+ "Priority" : "optional",
+ "Section" : "utils",
+ "Title" : "control and monitor storage systems using S.M.A.R.T.",
+ "Version" : "7.4-pve1"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "With spiceterm you can start commands and export its standard input and output to any SPICE client (simulating a xterm Terminal).",
+ "OldVersion" : "3.4.1",
+ "Origin" : "Proxmox",
+ "Package" : "spiceterm",
+ "Priority" : "optional",
+ "Section" : "admin",
+ "Title" : "SPICE Terminal Emulator",
+ "Version" : "3.4.1"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "The swtpm package provides TPM emulators that listen for TPM commands on sockets, character devices, or CUSE devices.",
+ "OldVersion" : "0.8.0+pve3",
+ "Origin" : "Proxmox",
+ "Package" : "swtpm",
+ "Priority" : "optional",
+ "Section" : "misc",
+ "Title" : "Libtpms-based TPM emulator",
+ "Version" : "0.8.0+pve3"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "With vncterm you can start commands and export its standard input and output to any VNC client (simulating a xterm Terminal).",
+ "OldVersion" : "1.9.1",
+ "Origin" : "Proxmox",
+ "Package" : "vncterm",
+ "Priority" : "optional",
+ "Section" : "x11",
+ "Title" : "VNC Terminal Emulator",
+ "Version" : "1.9.1"
+ },
+ {
+ "Arch" : "amd64",
+ "CurrentState" : "Installed",
+ "Description" : "OpenZFS is a storage platform that encompasses the functionality of traditional filesystems and volume managers. It supports data checksums, compression, encryption, snapshots, and more. . This package provides the zfs and zpool commands to create and administer OpenZFS filesystems.",
+ "OldVersion" : "2.3.4-pve1",
+ "Origin" : "Proxmox",
+ "Package" : "zfsutils-linux",
+ "Priority" : "optional",
+ "Section" : "contrib/admin",
+ "Title" : "command-line tools to manage OpenZFS filesystems",
+ "Version" : "2.4.0-pve1"
+ }
+]
diff --git a/server/tests/api_responses/pve/node_subscription.json b/server/tests/api_responses/pve/node_subscription.json
new file mode 100644
index 00000000..851bca39
--- /dev/null
+++ b/server/tests/api_responses/pve/node_subscription.json
@@ -0,0 +1,6 @@
+{
+ "message" : "There is no subscription key",
+ "serverid" : "DE0E8A66368D921A763F2B6AC6AA43DD",
+ "status" : "notfound",
+ "url" : "https://www.proxmox.com/en/proxmox-virtual-environment/pricing"
+}
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [RFC datacenter-manager 6/6] tests: add basic integration tests for the remote updates API
2026-01-29 13:44 [RFC datacenter-manager/proxmox 0/7] inject application context via rpcenv for easier integration testing Lukas Wagner
` (5 preceding siblings ...)
2026-01-29 13:44 ` [RFC datacenter-manager 5/6] tests: add captured responses for integration tests Lukas Wagner
@ 2026-01-29 13:44 ` Lukas Wagner
2026-02-03 11:02 ` [RFC datacenter-manager/proxmox 0/7] inject application context via rpcenv for easier integration testing Robert Obkircher
7 siblings, 0 replies; 9+ messages in thread
From: Lukas Wagner @ 2026-01-29 13:44 UTC (permalink / raw)
To: pdm-devel
To demonstrate that the principle works and can be used to meaningfully
test subsystems in PDM.
Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
server/src/lib.rs | 3 +-
server/src/test_support/mod.rs | 3 +-
server/tests/test_remote_updates.rs | 288 ++++++++++++++++++++++++++++
3 files changed, 292 insertions(+), 2 deletions(-)
create mode 100644 server/tests/test_remote_updates.rs
diff --git a/server/src/lib.rs b/server/src/lib.rs
index 5ed10d69..af847286 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -20,7 +20,8 @@ pub mod connection;
pub mod pbs_client;
pub mod sdn_client;
-#[cfg(any(remote_config = "faked", test))]
+// FIXME:
+// #[cfg(any(remote_config = "faked", test))]
pub mod test_support;
use anyhow::Error;
diff --git a/server/src/test_support/mod.rs b/server/src/test_support/mod.rs
index f026011c..b7b2b8e1 100644
--- a/server/src/test_support/mod.rs
+++ b/server/src/test_support/mod.rs
@@ -1,5 +1,6 @@
#[cfg(remote_config = "faked")]
pub mod fake_remote;
-#[cfg(test)]
+// FIXME:
+// #[cfg(test)]
pub mod temp;
diff --git a/server/tests/test_remote_updates.rs b/server/tests/test_remote_updates.rs
new file mode 100644
index 00000000..b34beac9
--- /dev/null
+++ b/server/tests/test_remote_updates.rs
@@ -0,0 +1,288 @@
+use std::any::Any;
+use std::sync::{Arc, Once};
+
+use anyhow::{bail, Error};
+use serde::de::DeserializeOwned;
+
+use proxmox_router::RpcEnvironment;
+use proxmox_section_config::typed::SectionConfigData;
+use proxmox_sys::fs::CreateOptions;
+use pve_api_types::ClusterNodeIndexResponse;
+
+use pdm_api_types::remote_updates::{PackageVersion, ProductRepositoryStatus, RemoteUpdateStatus};
+use pdm_api_types::Authid;
+use pdm_api_types::{remotes::Remote, ConfigDigest};
+use pdm_config::remotes::RemoteConfig;
+
+use server::connection::{ClientFactory, DefaultClientFactory, PveClient};
+use server::context::PdmApplication;
+use server::pbs_client::PbsClient;
+use server::test_support::temp::NamedTempDir;
+
+struct TestRpcEnv {
+ context: Arc<dyn Any + Send + Sync>,
+}
+
+impl TestRpcEnv {
+ fn new(app: PdmApplication) -> Self {
+ Self {
+ context: Arc::new(app),
+ }
+ }
+}
+
+impl RpcEnvironment for TestRpcEnv {
+ fn result_attrib_mut(&mut self) -> &mut serde_json::Value {
+ unimplemented!()
+ }
+
+ fn result_attrib(&self) -> &serde_json::Value {
+ unimplemented!()
+ }
+
+ fn env_type(&self) -> proxmox_router::RpcEnvironmentType {
+ unimplemented!()
+ }
+
+ fn set_auth_id(&mut self, _user: Option<String>) {
+ unimplemented!()
+ }
+
+ fn get_auth_id(&self) -> Option<String> {
+ Some("root@pam".to_string())
+ }
+
+ fn application_context(&self) -> Option<Arc<dyn Any + Send + Sync>> {
+ Some(Arc::clone(&self.context))
+ }
+}
+
+struct MockRemoteConfig;
+
+impl RemoteConfig for MockRemoteConfig {
+ fn config(&self) -> Result<(SectionConfigData<Remote>, ConfigDigest), anyhow::Error> {
+ let mut sections = SectionConfigData::default();
+
+ for i in 0..4 {
+ let name = format!("pve-{i}");
+
+ sections.insert(
+ name.clone(),
+ Remote {
+ ty: pdm_api_types::remotes::RemoteType::Pve,
+ id: name.clone(),
+ nodes: Vec::new(),
+ authid: Authid::root_auth_id().clone(),
+ token: "".into(),
+ web_url: None,
+ },
+ );
+ }
+
+ Ok((sections, ConfigDigest::from_slice(&[])))
+ }
+
+ fn get_secret_token(
+ &self,
+ _remote: &pdm_api_types::remotes::Remote,
+ ) -> Result<String, anyhow::Error> {
+ unimplemented!()
+ }
+
+ fn lock_config(&self) -> Result<proxmox_product_config::ApiLockGuard, anyhow::Error> {
+ unimplemented!()
+ }
+
+ fn save_config(
+ &self,
+ _remotes: proxmox_section_config::typed::SectionConfigData<pdm_api_types::remotes::Remote>,
+ ) -> Result<(), anyhow::Error> {
+ unimplemented!()
+ }
+}
+
+struct TestClientFactory;
+
+#[async_trait::async_trait]
+impl ClientFactory for TestClientFactory {
+ fn make_pve_client(&self, _remote: &Remote) -> Result<Arc<PveClient>, Error> {
+ Ok(Arc::new(TestPveClient {}))
+ }
+ /// Create a new API client for PVE remotes, but with a specific endpoint.
+ fn make_pve_client_with_endpoint(
+ &self,
+ _remote: &Remote,
+ _target_endpoint: Option<&str>,
+ ) -> Result<Arc<PveClient>, Error> {
+ bail!("not implemented")
+ }
+
+ fn make_pbs_client(&self, _remote: &Remote) -> Result<Box<PbsClient>, Error> {
+ bail!("not implemented")
+ }
+
+ fn make_raw_client(&self, _remote: &Remote) -> Result<Box<proxmox_client::Client>, Error> {
+ bail!("not implemented")
+ }
+
+ async fn make_pve_client_and_login(&self, _remote: &Remote) -> Result<Arc<PveClient>, Error> {
+ bail!("not implemented")
+ }
+
+ async fn make_pbs_client_and_login(&self, _remote: &Remote) -> Result<Box<PbsClient>, Error> {
+ bail!("not implemented")
+ }
+}
+
+struct TestPveClient {}
+
+#[async_trait::async_trait]
+impl pve_api_types::client::PveClient for TestPveClient {
+ async fn list_available_updates(
+ &self,
+ _node: &str,
+ ) -> Result<Vec<pve_api_types::AptUpdateInfo>, proxmox_client::Error> {
+ let s = tokio::fs::read_to_string("tests/api_responses/pve/apt_update.json")
+ .await
+ .unwrap();
+
+ Ok(serde_json::from_str(&s).unwrap())
+ }
+
+ /// Cluster node index.
+ async fn list_nodes(
+ &self,
+ ) -> Result<Vec<pve_api_types::ClusterNodeIndexResponse>, proxmox_client::Error> {
+ let mut nodes = Vec::new();
+
+ for i in 0..3 {
+ nodes.push(ClusterNodeIndexResponse {
+ cpu: Some(0.0),
+ level: None,
+ maxcpu: Some(4),
+ maxmem: Some(4096),
+ mem: Some(1000),
+ node: format!("node-{i}"),
+ ssl_fingerprint: None,
+ status: pve_api_types::ClusterNodeIndexResponseStatus::Online,
+ uptime: Some(1000),
+ });
+ }
+
+ Ok(nodes)
+ }
+
+ /// Get package information for important Proxmox packages.
+ async fn get_package_versions(
+ &self,
+ _node: &str,
+ ) -> Result<Vec<pve_api_types::InstalledPackage>, proxmox_client::Error> {
+ read_captured_response("tests/api_responses/pve/apt_versions.json").await
+ }
+
+ /// Get APT repository information.
+ async fn get_apt_repositories(
+ &self,
+ _node: &str,
+ ) -> Result<pve_api_types::APTRepositoriesResult, proxmox_client::Error> {
+ read_captured_response("tests/api_responses/pve/apt_repos.json").await
+ }
+
+ /// Read subscription info.
+ async fn get_subscription(
+ &self,
+ _node: &str,
+ ) -> Result<pve_api_types::NodeSubscriptionInfo, proxmox_client::Error> {
+ read_captured_response("tests/api_responses/pve/node_subscription.json").await
+ }
+}
+
+async fn read_captured_response<T: DeserializeOwned>(
+ path: &str,
+) -> Result<T, proxmox_client::Error> {
+ let s = tokio::fs::read_to_string(path).await.unwrap();
+ Ok(serde_json::from_str(&s).unwrap())
+}
+
+fn test_setup() {
+ static INIT: Once = Once::new();
+
+ INIT.call_once(|| {
+ // FIXME: As long as we are 'root@pam' and do not add any new users or change the user
+ // config this *should* be fine
+ proxmox_access_control::init::init(&pdm_api_types::AccessControlConfig, "/tmp")
+ .expect("failed to setup access control config");
+ let file_opts = CreateOptions::new();
+
+ // TODO: Create some kind of temporary directory which exists for the entire
+ // duration of the test run.
+ proxmox_rest_server::init_worker_tasks("/tmp".into(), file_opts).unwrap();
+ });
+}
+
+#[tokio::test]
+async fn update_summary_unknown_if_not_polled() {
+ test_setup();
+
+ let test_dir = NamedTempDir::new().unwrap();
+
+ // TODO: Maybe some kind of builder pattern for PdmApplication would be nice
+ let mut env = TestRpcEnv::new(PdmApplication {
+ remote_config: Arc::new(MockRemoteConfig),
+ client_factory: Arc::new(DefaultClientFactory),
+ config_path: test_dir.path().into(),
+ cache_path: test_dir.path().into(),
+ default_create_options: CreateOptions::new(),
+ });
+
+ let summary = server::api::remote_updates::update_summary(&mut env).unwrap();
+
+ for val in summary.remotes.values() {
+ assert_eq!(val.status, RemoteUpdateStatus::Unknown);
+ }
+}
+
+#[tokio::test]
+async fn update_summary_populated_after_update() {
+ test_setup();
+
+ let test_dir = NamedTempDir::new().unwrap();
+ dbg!(test_dir.path());
+
+ // TODO: Maybe some kind of builder pattern for PdmApplication would be nice
+ let mut env = TestRpcEnv::new(PdmApplication {
+ remote_config: Arc::new(MockRemoteConfig),
+ client_factory: Arc::new(TestClientFactory),
+ config_path: test_dir.path().into(),
+ cache_path: test_dir.path().into(),
+ default_create_options: CreateOptions::new(),
+ });
+
+ let upid = server::api::remote_updates::refresh_remote_update_summaries(&mut env).unwrap();
+ proxmox_rest_server::wait_for_local_worker(&upid.to_string())
+ .await
+ .unwrap();
+
+ let summary = server::api::remote_updates::update_summary(&mut env).unwrap();
+
+ for val in summary.remotes.values() {
+ assert_eq!(val.status, RemoteUpdateStatus::Success);
+
+ for node_data in val.nodes.values() {
+ assert_eq!(
+ node_data.versions,
+ vec![PackageVersion {
+ package: "pve-manager".into(),
+ version: "9.1.2".into()
+ }]
+ );
+
+ assert_eq!(
+ node_data.repository_status,
+ ProductRepositoryStatus::NonProductionReady
+ );
+
+ assert_eq!(node_data.number_of_updates, 58);
+ }
+ }
+}
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [RFC datacenter-manager/proxmox 0/7] inject application context via rpcenv for easier integration testing
2026-01-29 13:44 [RFC datacenter-manager/proxmox 0/7] inject application context via rpcenv for easier integration testing Lukas Wagner
` (6 preceding siblings ...)
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 ` Robert Obkircher
7 siblings, 0 replies; 9+ messages in thread
From: Robert Obkircher @ 2026-02-03 11:02 UTC (permalink / raw)
To: pdm-devel
On 1/29/26 14:43, Lukas Wagner wrote:
> TL;DR: Inject essential runtime config, client factory, etc. as an application
> context object and allow to retrieve this object easily in an API handler via
> rpcenv. This allows us directly call API handler implementations from
> integration tests. The aim is to make it easier to write good tests on a larger
> level.
>
> ## Introduction
>
> Considering the different stages of automated software testing, unit testing
> (test small software components in isolation) integration testing (test
> multiple components to together, specifically their interactions) and
> end-to-end testing (test the entire application in a context as close to
> production as possible), I've found that the middle one, integration testing is
> a very challenging one in our stack.
>
> I've found that the challenges with integration testing mostly stem from the
> following:
>
> - "hardcoded" (as in, determined by some constant or literally
> hard-coded) assumptions about storage paths (config, state, caches) and
> users/permissions. This makes it challenging to call into the component
> from a test running as normal user, e.g. from a regular `cargo test`.
>
> - use of global/static instances (examples: Worker task context,
> proxmox-product-config, client factory in PDM...) in application code and
> shared crates. While this can be okay for things that are truly global (e.g.
> logging), it often hinders testing and especially test isolation due to hidden
> dependencies between test cases and some potential internal state of the global
> instance. This is one of the common causes of flaky tests. Also, the setup of
> these global instances in test cases is always a bit awkward, since the order
> of test execution is not defined (and might actually run in parallel), so some
> kind of synchronization between the test cases is necessary. Furthermore,
> certain test cases might require a *different* setup for these global instances
> (example: client factory that should produce a different kind of mocked PVE
> client); but since we usually put these in OnceLocks, one has to put these
> tests into a separate test binary.
>
> - tight coupling between major subsystems (e.g. one part calling into the
> other without any clear boundary or abstraction via callbacks or traits)
>
>
> With regards to PDM, there are a couple of things that need considering when testing:
> - Filesystem access to config, state and caches
> - Interactions with remotes via their API
>
> I feel like if we find a sensible way to abstract these for tests, we can
> resonably cover a huge amount of the code in the backend.
>
> ## Implementation
>
> The core idea I want to discuss in this RFC is to provide a context/application
> object ('struct PdmApplication') and "injecting" it into out API handlers via
> the already existing rpcenv variable.
>
> This new context object would give access to:
> - base paths for caches, config, state
> - file permissions, user/group
> - client factory
> - depending on what we need, potentially other things
>
> So, for instance, instead of calling into the `connection` module to create a
> PVE client, one could retrieve the client factory instance from the application
> object and create the application from that:
>
> So the following
>
> let client = connection::make_pve_client(...)
>
> becomes
>
> // app is of type `PdmApplication`
> let client = app.client_factory().make_pve_client(...)
>
> And
>
> let config_path = configdir!("foo.cfg")
>
> becomes
>
> let config_path = app.config_path().join("foo.cfg")
>
> NOTE: The exact structure of this object and what kind of methods it provides
> is not fixed yet.
>
>
> ## Where does `PdmApplication` come from?
>
> For the regular API daemon, the PdmApplication context object is set up during
> the daemon startup routine.
>
> A handle to it is then associated with the REST server and stored in the
> RpcEnvironment that is passed to the API handlers. In it's current form this
> entails some ugly `dyn Any` hacks, but I think it's not too bad. Any API
> handler can retrieve the application object from rpcenv, e.g. like this:
>
> // API handler implementation
> async fn apt_get_changelog(
> remote: String,
> node: String,
> options: APTGetChangelogOptions,
> rpcenv: &mut dyn RpcEnvironment,
> ) -> Result<String, Error> {
> // `get_application` simply does the downcast from `Any` to the correct type.
> // RpcEnvironment has to hold it as `Any` since the `PdmApplication` type is
> // of course specific to the application itself.
> //
> // NOTE: app is of type Arc<PdmApplication>
> let app = context::get_application(rpcenv);
>
> // For configs that might to be mocked for tests, the app object could
> // also provide some form of accessor:
> let (config, _digest) = app.remote_config().config()?;
> let remote = get_remote(&config, &remote)?;
>
> remote_updates::get_changelog(&app, remote, &node, options.name).await
> }
>
> This application handle would need to be propagated down the call stack, either
> as a &PdmApplication or a Arc<PdmApplication>:
>
> // actual implementation of the functionality somewhere else in the codebase
> pub async fn get_changelog(
> app: &PdmApplication,
> remote: &Remote,
> node: &str,
> package: String,
> ) -> Result<String, Error> {
> ...
> let client = app.client_factory().make_pve_client(remote)?;
> ...
> }
>
>
> Code that is executed independently from API handlers, e.g. periodic tasks,
> would receive their application handle during setup (as a clone of the
> 'original' Arc<PdmApplication> that was injected into the REST server).
>
> This general pattern of injecting application state/context via a parameter
> to the handler is something that is also common in other Rust-based
> web framworks, e.g. [actix-web] and [axum].
>
>
> ## Interaction with shared crates
>
> Quite a few of our shared crates in proxmox.git have some form of static/global
> context that has to be initialized, for instance:
>
> - proxmox-notify
> - proxmox-rest-server (worker task setup)
> - proxmox-product-config
> - proxmox-access-control
>
> I think long-term, if possible, we should try to avoid having static context in
> crates completely. For instance, at some point I'll probably refactor
> proxmox-notify so that the context is always passed along with any call to the
> crate, instead of setting it up statically. As a stop-gap, this context can
> still be static in the *application*, but with everything else I mentioned in
> this RFC so far, I think that it should in some form be derived from the
> general PdmApplication context object.
>
> One thing that could work is implementing a crate-specific context trait for the
> application object itself and then passing the application itself:
>
> impl proxmox_notify::Context for PdmApplication {
> ...
> }
>
> proxmox_notify::send(&app, ¬ification)?;
>
> Or, alternatively, the `app` object would be used to construct some kind of other
> context object for the crate:
>
> struct PdmNotifyContext {
> ...
> }
>
> impl proxmox_notify::Context for PdmNotifyContext {
> ...
> }
>
> impl PdmNotifyContext {
> fn from_app(app: &PdmApplication) {
> ...
> }
> }
>
> let notify_context = PdmNotifyContext::from_app(app);
> proxmox_notify::send(¬ify_context, ¬ification)?;
>
>
> I have not yet written any code for these shared crate setups, but I think the
> general pattern *should* work.
>
> What I'm not so sure about yet and where I have not done any experiments is the
> case where the entire API handler is defined in a shared crate. These of course
> cannot access the product-specific struct. Many of these rely on some form of
> static initialization, either directly or indirectly (e.g.
> proxmox-product-config).
>
> I'd be happy about any ideas for these cases.
I'm not very familiar with any of this, but have you considered making
dependency injection more flexible?
If API handlers can choose what they want (e.g. with a InjectedRef<T>
parameters) they never need to know about an application specific type
like PdmApplication.
Tests would then also now exactly which dependencies to construct and
they could just pass them by reference.
As far as I can tell, Actix [1] also seems to support multiple types
for app_data and Axum has a slightly different approach with the
FromRef trait used for substates [2].
[1]
https://docs.rs/actix-web/latest/actix_web/struct.Scope.html#method.app_data
[2] https://docs.rs/axum/latest/axum/extract/struct.State.html#substates
>
> Personally I'm not a big fan of defining the entire API handlers in the shared
> crate, I rather prefer having the handler definitions in the application code
> and having this handler call *into* the shared crate - I think this gives more
> flexibility in case there need to be some application-specific changes (e.g.
> additional parameters, different types, etc.). Also in this case it would be
> easy to pass along an additional context object along with the call.
>
> ## Summary
>
> Pros:
> - We can reasonably test subsystems in isolation with the comfort of a simple `cargo test`
> - I think this RFC shows that it's definitely possible to adapt to this new pattern
> gradually without having to refactor huge amounts of existing code at once.
>
> Risks:
> - Increased complexity
> - Having to pass some kind of application handle down the callstack
> could be considered quite invasive.
> - Having to deal with Box'd/Arc'd trait objects (e.g. the client factory)
> can be a bit annoying
> - The application context object
>
> - While this approach does not require us to commit fully right from the start, it could
> be quite awkward if we have "the old approach" and the "new approach" in parallel.
> So it would be important that we agree on the sensibility of this approach and then
> consequently use it for new features and then over time gradually introduce it
> into the existing code.
>
>
> The patches submitted in this patch series are of course still work in
> progress; their main aim is to demonstrate that viability of the concept.
>
> I'd be very happy to get some feedback on the general idea and of course also
> the PoC implementation (but don't get too caught up in naming, missing doc
> comments, etc - as I said, everything is still work in progress).
>
> ## References:
>
> [actix-web]: https://actix.rs/docs/application/#state
> [axum]: https://docs.rs/axum/latest/axum/#sharing-state-with-handlers
>
>
> proxmox:
>
> Lukas Wagner (1):
> router: rpc environment: allow to provide a application-specific
> context handle via rpcenv
>
> proxmox-rest-server/src/api_config.rs | 10 ++++++++++
> proxmox-rest-server/src/environment.rs | 6 +++++-
> proxmox-router/src/cli/environment.rs | 6 ++++++
> proxmox-router/src/rpc_environment.rs | 5 ++++-
> 4 files changed, 25 insertions(+), 2 deletions(-)
>
>
> proxmox-datacenter-manager:
>
> Lukas Wagner (6):
> connection: store client factory in an Arc and add public getter
> parallel fetcher: allow to use custom client factory
> introduce PdmApplication struct and inject it during API server
> startup
> remote updates: use PdmApplication object to derive paths, permissions
> and client factory
> tests: add captured responses for integration tests
> tests: add basic integration tests for the remote updates API
>
> server/src/api/pve/mod.rs | 11 +-
> server/src/api/remote_updates.rs | 54 +-
> server/src/bin/proxmox-datacenter-api/main.rs | 9 +-
> .../tasks/remote_tasks.rs | 2 +
> .../tasks/remote_updates.rs | 22 +-
> .../bin/proxmox-datacenter-privileged-api.rs | 7 +-
> server/src/connection.rs | 29 +-
> server/src/context.rs | 74 +-
> server/src/lib.rs | 3 +-
> .../src/metric_collection/collection_task.rs | 2 +-
> server/src/parallel_fetcher.rs | 24 +-
> server/src/remote_updates.rs | 90 ++-
> server/src/test_support/mod.rs | 3 +-
> server/tests/api_responses/pve/apt_repos.json | 469 +++++++++++
> .../tests/api_responses/pve/apt_update.json | 638 +++++++++++++++
> .../tests/api_responses/pve/apt_versions.json | 736 ++++++++++++++++++
> .../api_responses/pve/node_subscription.json | 6 +
> server/tests/test_remote_updates.rs | 288 +++++++
> 18 files changed, 2394 insertions(+), 73 deletions(-)
> create mode 100644 server/tests/api_responses/pve/apt_repos.json
> create mode 100644 server/tests/api_responses/pve/apt_update.json
> create mode 100644 server/tests/api_responses/pve/apt_versions.json
> create mode 100644 server/tests/api_responses/pve/node_subscription.json
> create mode 100644 server/tests/test_remote_updates.rs
>
>
> Summary over all repositories:
> 22 files changed, 2419 insertions(+), 75 deletions(-)
>
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-02-03 11:02 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 ` [RFC datacenter-manager 4/6] remote updates: use PdmApplication object to derive paths, permissions and client factory Lukas Wagner
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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox