From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id 2A0261FF0AA for ; Fri, 21 Aug 2026 14:32:13 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 2844C21607; Fri, 21 Aug 2026 14:32:07 +0200 (CEST) From: Dominik Csapak To: pdm-devel@lists.proxmox.com Subject: [PATCH datacenter-manager v3 1/9] lib: api types: add new ResourceView type and move accessors there Date: Fri, 21 Aug 2026 14:30:13 +0200 Message-ID: <20260821123201.3035643-2-d.csapak@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260821123201.3035643-1-d.csapak@proxmox.com> References: <20260821123201.3035643-1-d.csapak@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.807 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: A6GDQCDMD4GLDHJJW6E4RYZW4PX7CCXI X-Message-ID-Hash: A6GDQCDMD4GLDHJJW6E4RYZW4PX7CCXI X-MailFrom: d.csapak@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Datacenter Manager development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: This is intended as a view type for Resource and PveResource, which will always share some enum type. This way, we can reuse all accessors cheaply for PveResource values without cloning or consuming. This makes it easier to share code for both of them, for instance in the UI where we want to render properties consistently for resources. Introduces also an AsResourceView trait to make converting more ergonomic. Signed-off-by: Dominik Csapak --- lib/pdm-api-types/src/resource.rs | 273 +++++++++++++++++++++--------- 1 file changed, 196 insertions(+), 77 deletions(-) diff --git a/lib/pdm-api-types/src/resource.rs b/lib/pdm-api-types/src/resource.rs index 303ac3cb..05cf297c 100644 --- a/lib/pdm-api-types/src/resource.rs +++ b/lib/pdm-api-types/src/resource.rs @@ -45,102 +45,36 @@ impl Resource { /// Returns the local ID, not a globally unique one, e.g. /// `qemu/` pub fn id(&self) -> String { - match self { - Resource::PveStorage(r) => format!("storage/{}/{}", r.node, r.storage), - Resource::PveQemu(r) => format!("qemu/{}", r.vmid), - Resource::PveLxc(r) => format!("lxc/{}", r.vmid), - Resource::PveNode(r) => format!("node/{}", r.node), - Resource::PveNetwork(r) => { - if let PveNetworkResource::Zone(z) = r { - if z.legacy { - return format!("sdn/{}/{}", r.node(), r.name()); - } - } - - format!("network/{}/{}/{}", r.node(), r.network_type(), r.name()) - } - Resource::PbsNode(r) => format!("node/{}", r.name), - Resource::PbsDatastore(r) => r.name.clone(), - } + self.as_resource_view().id() } /// Returns the PDM global ID for the resource, e.g. /// `remote//guest/` pub fn global_id(&self) -> &str { - match self { - Resource::PveStorage(r) => r.id.as_str(), - Resource::PveQemu(r) => r.id.as_str(), - Resource::PveLxc(r) => r.id.as_str(), - Resource::PveNode(r) => r.id.as_str(), - Resource::PveNetwork(r) => r.id(), - Resource::PbsNode(r) => r.id.as_str(), - Resource::PbsDatastore(r) => r.id.as_str(), - } + self.as_resource_view().global_id() } /// Returns the "name" of the resource, e.g. the guest name for VMs/Containers or /// the hostname for nodes pub fn name(&self) -> &str { - match self { - Resource::PveStorage(r) => r.storage.as_str(), - Resource::PveQemu(r) => r.name.as_str(), - Resource::PveLxc(r) => r.name.as_str(), - Resource::PveNode(r) => r.node.as_str(), - Resource::PveNetwork(r) => r.name(), - Resource::PbsNode(r) => r.name.as_str(), - Resource::PbsDatastore(r) => r.name.as_str(), - } + self.as_resource_view().name() } + /// Returns the resource type, e.g. Node, Qemu, etc. pub fn resource_type(&self) -> ResourceType { - match self { - Resource::PveStorage(_) => ResourceType::PveStorage, - Resource::PveQemu(_) => ResourceType::PveQemu, - Resource::PveLxc(_) => ResourceType::PveLxc, - Resource::PveNetwork(_) => ResourceType::PveNetwork, - Resource::PveNode(_) | Resource::PbsNode(_) => ResourceType::Node, - Resource::PbsDatastore(_) => ResourceType::PbsDatastore, - } + self.as_resource_view().resource_type() } + /// Returns the status of the resource, e.g. 'online' or 'offline'. The exact + /// status values depends on the resource type. pub fn status(&self) -> &str { - match self { - Resource::PveStorage(r) => r.status.as_str(), - Resource::PveQemu(r) => r.status.as_str(), - Resource::PveLxc(r) => r.status.as_str(), - Resource::PveNode(r) => r.status.as_str(), - Resource::PveNetwork(r) => r.status(), - Resource::PbsNode(r) => { - if r.uptime > 0 { - "online" - } else { - "offline" - } - } - Resource::PbsDatastore(r) => { - if r.maintenance.is_none() { - "online" - } else { - "under-maintenance" - } - } - } + self.as_resource_view().status() } + /// Returns additional properties of the resource. Only a PBS datastore + /// returns values. pub fn properties(&self) -> String { - let mut properties = Vec::new(); - if let Resource::PbsDatastore(r) = self { - if let Some(backend_type) = &r.backend_type { - properties.push(backend_type.to_string()); - } - if r.backing_device.is_some() { - properties.push("removable".to_string()); - } - if r.usage > PBS_DATASTORE_HIGH_USAGE_THRESHOLD { - properties.push("high-usage".to_string()); - } - } - properties.join(",") + self.as_resource_view().properties() } } @@ -229,6 +163,191 @@ pub enum PveResource { Network(PveNetworkResource), } +/// A borrowed view of a resource that can come from a [Resource] or a [PveResource] This is useful +/// for having a single implementation of field access regardless of which of the types is used. +#[derive(Clone, Debug, PartialEq)] +pub enum ResourceView<'a> { + /// Storage resource in PVE. + PveStorage(&'a PveStorageResource), + /// QEMU guest resource in PVE. + PveQemu(&'a PveQemuResource), + /// LXC guest resource in PVE. + PveLxc(&'a PveLxcResource), + /// A PVE node. + PveNode(&'a PveNodeResource), + /// Network in PVE. + PveNetwork(&'a PveNetworkResource), + /// A PBS node. + PbsNode(&'a PbsNodeResource), + /// Datastore on a PBS node. + PbsDatastore(&'a PbsDatastoreResource), +} + +impl<'a> ResourceView<'a> { + /// Returns the local ID, not a globally unique one, e.g. + /// `qemu/` + pub fn id(&self) -> String { + match self { + ResourceView::PveStorage(r) => format!("storage/{}/{}", r.node, r.storage), + ResourceView::PveQemu(r) => format!("qemu/{}", r.vmid), + ResourceView::PveLxc(r) => format!("lxc/{}", r.vmid), + ResourceView::PveNode(r) => format!("node/{}", r.node), + ResourceView::PveNetwork(r) => { + if let PveNetworkResource::Zone(z) = r { + if z.legacy { + return format!("sdn/{}/{}", r.node(), r.name()); + } + } + + format!("network/{}/{}/{}", r.node(), r.network_type(), r.name()) + } + ResourceView::PbsNode(r) => format!("node/{}", r.name), + ResourceView::PbsDatastore(r) => r.name.clone(), + } + } + + /// Returns the PDM global ID for the resource, e.g. + /// `remote//guest/` + pub fn global_id(&self) -> &'a str { + match self { + ResourceView::PveStorage(r) => r.id.as_str(), + ResourceView::PveQemu(r) => r.id.as_str(), + ResourceView::PveLxc(r) => r.id.as_str(), + ResourceView::PveNode(r) => r.id.as_str(), + ResourceView::PveNetwork(r) => r.id(), + ResourceView::PbsNode(r) => r.id.as_str(), + ResourceView::PbsDatastore(r) => r.id.as_str(), + } + } + + /// Returns the "name" of the resource, e.g. the guest name for VMs/Containers or + /// the hostname for nodes + pub fn name(&self) -> &'a str { + match self { + ResourceView::PveStorage(r) => r.storage.as_str(), + ResourceView::PveQemu(r) => r.name.as_str(), + ResourceView::PveLxc(r) => r.name.as_str(), + ResourceView::PveNode(r) => r.node.as_str(), + ResourceView::PveNetwork(r) => r.name(), + ResourceView::PbsNode(r) => r.name.as_str(), + ResourceView::PbsDatastore(r) => r.name.as_str(), + } + } + + /// Returns the resource type, e.g. Node, Qemu, etc. + pub fn resource_type(&self) -> ResourceType { + match self { + ResourceView::PveStorage(_) => ResourceType::PveStorage, + ResourceView::PveQemu(_) => ResourceType::PveQemu, + ResourceView::PveLxc(_) => ResourceType::PveLxc, + ResourceView::PveNetwork(_) => ResourceType::PveNetwork, + ResourceView::PveNode(_) | ResourceView::PbsNode(_) => ResourceType::Node, + ResourceView::PbsDatastore(_) => ResourceType::PbsDatastore, + } + } + + /// Returns the status of the resource, e.g. 'online' or 'offline'. The exact + /// status values depends on the resource type. + pub fn status(&self) -> &'a str { + match self { + ResourceView::PveStorage(r) => r.status.as_str(), + ResourceView::PveQemu(r) => r.status.as_str(), + ResourceView::PveLxc(r) => r.status.as_str(), + ResourceView::PveNode(r) => r.status.as_str(), + ResourceView::PveNetwork(r) => r.status(), + ResourceView::PbsNode(r) => { + if r.uptime > 0 { + "online" + } else { + "offline" + } + } + ResourceView::PbsDatastore(r) => { + if r.maintenance.is_none() { + "online" + } else { + "under-maintenance" + } + } + } + } + + /// Returns additional properties of the resource. Only a PBS datastore + /// returns values. + pub fn properties(&self) -> String { + let mut properties = Vec::new(); + if let ResourceView::PbsDatastore(r) = self { + if let Some(backend_type) = &r.backend_type { + properties.push(backend_type.to_string()); + } + if r.backing_device.is_some() { + properties.push("removable".to_string()); + } + if r.usage > PBS_DATASTORE_HIGH_USAGE_THRESHOLD { + properties.push("high-usage".to_string()); + } + } + properties.join(",") + } +} + +impl<'a> From<&'a Resource> for ResourceView<'a> { + fn from(value: &'a Resource) -> Self { + match value { + Resource::PveStorage(r) => ResourceView::PveStorage(r), + Resource::PveQemu(r) => ResourceView::PveQemu(r), + Resource::PveLxc(r) => ResourceView::PveLxc(r), + Resource::PveNode(r) => ResourceView::PveNode(r), + Resource::PveNetwork(r) => ResourceView::PveNetwork(r), + Resource::PbsNode(r) => ResourceView::PbsNode(r), + Resource::PbsDatastore(r) => ResourceView::PbsDatastore(r), + } + } +} + +impl<'a> From<&'a PveResource> for ResourceView<'a> { + fn from(value: &'a PveResource) -> Self { + match value { + PveResource::Storage(r) => ResourceView::PveStorage(r), + PveResource::Qemu(r) => ResourceView::PveQemu(r), + PveResource::Lxc(r) => ResourceView::PveLxc(r), + PveResource::Node(r) => ResourceView::PveNode(r), + PveResource::Network(r) => ResourceView::PveNetwork(r), + } + } +} + +/// A trait to that allows any struct which contains resources (e.g. PveStorageResource, +/// PveQemuResource, etc.) to be represented with a lifetime, so a reference. This makes it +/// possible to use a reference to such a resource in a generic manner. +pub trait AsResourceView { + fn as_resource_view(&self) -> ResourceView<'_>; +} + +impl AsResourceView for Resource { + fn as_resource_view<'a>(&'a self) -> ResourceView<'a> { + ResourceView::from(self) + } +} + +impl AsResourceView for &Resource { + fn as_resource_view<'a>(&'a self) -> ResourceView<'a> { + ResourceView::from(*self) + } +} + +impl AsResourceView for PveResource { + fn as_resource_view<'a>(&'a self) -> ResourceView<'a> { + ResourceView::from(self) + } +} + +impl AsResourceView for &PveResource { + fn as_resource_view<'a>(&'a self) -> ResourceView<'a> { + ResourceView::from(*self) + } +} + #[api( properties: { tags: { -- 2.47.3