nv_redfish/update_service/mod.rs
1// SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Update Service entities and collections.
17//!
18//! This module provides types for working with Redfish UpdateService resources
19//! and their sub-resources like firmware and software inventory.
20
21mod software_inventory;
22
23use crate::patch_support::ReadPatchFn;
24use crate::schema::redfish::update_service::UpdateService as UpdateServiceSchema;
25use crate::schema::redfish::update_service::UpdateServiceSimpleUpdateAction;
26use crate::Error;
27use crate::NvBmc;
28use crate::Resource;
29use crate::ResourceSchema;
30use crate::ServiceRoot;
31use nv_redfish_core::Bmc;
32use serde_json::Value as JsonValue;
33use software_inventory::SoftwareInventoryCollection;
34use std::sync::Arc;
35
36#[doc(inline)]
37pub use crate::schema::redfish::update_service::TransferProtocolType;
38#[doc(inline)]
39pub use software_inventory::SoftwareInventory;
40#[doc(inline)]
41pub use software_inventory::Version;
42#[doc(inline)]
43pub use software_inventory::VersionRef;
44
45/// Update service.
46///
47/// Provides functions to access firmware and software inventory, and perform update actions.
48pub struct UpdateService<B: Bmc> {
49 bmc: NvBmc<B>,
50 data: Arc<UpdateServiceSchema>,
51 fw_inventory_read_patch_fn: Option<ReadPatchFn>,
52}
53
54impl<B: Bmc> UpdateService<B> {
55 /// Create a new update service handle.
56 pub(crate) async fn new(bmc: &NvBmc<B>, root: &ServiceRoot<B>) -> Result<Self, Error<B>> {
57 let service_ref = root
58 .root
59 .update_service
60 .as_ref()
61 .ok_or(Error::UpdateServiceNotSupported)?;
62 let data = service_ref.get(bmc.as_ref()).await.map_err(Error::Bmc)?;
63
64 let mut fw_inventory_patches = Vec::new();
65 if root.fw_inventory_wrong_release_date() {
66 fw_inventory_patches.push(fw_inventory_patch_wrong_release_date);
67 }
68 let fw_inventory_read_patch_fn = if fw_inventory_patches.is_empty() {
69 None
70 } else {
71 let fw_inventory_patches_fn: ReadPatchFn =
72 Arc::new(move |v| fw_inventory_patches.iter().fold(v, |acc, f| f(acc)));
73 Some(fw_inventory_patches_fn)
74 };
75 Ok(Self {
76 bmc: bmc.clone(),
77 data,
78 fw_inventory_read_patch_fn,
79 })
80 }
81
82 /// Get the raw schema data for this update service.
83 ///
84 /// Returns an `Arc` to the underlying schema, allowing cheap cloning
85 /// and sharing of the data.
86 #[must_use]
87 pub fn raw(&self) -> Arc<UpdateServiceSchema> {
88 self.data.clone()
89 }
90
91 /// List all firmware inventory items.
92 ///
93 /// # Errors
94 ///
95 /// Returns an error if:
96 /// - The update service does not have a firmware inventory collection
97 /// - Fetching firmware inventory data fails
98 pub async fn firmware_inventories(&self) -> Result<Vec<SoftwareInventory<B>>, Error<B>> {
99 let collection_ref = self
100 .data
101 .firmware_inventory
102 .as_ref()
103 .ok_or(Error::FirmwareInventoryNotAvailable)?;
104
105 SoftwareInventoryCollection::new(
106 &self.bmc,
107 collection_ref,
108 self.fw_inventory_read_patch_fn.clone(),
109 )
110 .await?
111 .members()
112 .await
113 }
114
115 /// List all software inventory items.
116 ///
117 /// # Errors
118 ///
119 /// Returns an error if:
120 /// - The update service does not have a software inventory collection
121 /// - Fetching software inventory data fails
122 pub async fn software_inventories(&self) -> Result<Vec<SoftwareInventory<B>>, Error<B>> {
123 let collection_ref = self
124 .data
125 .software_inventory
126 .as_ref()
127 .ok_or(Error::SoftwareInventoryNotAvailable)?;
128 let collection = self.bmc.expand_property(collection_ref).await?;
129 let mut items = Vec::new();
130 for item_ref in &collection.members {
131 items.push(SoftwareInventory::new(&self.bmc, item_ref, None).await?);
132 }
133 Ok(items)
134 }
135
136 /// Perform a simple update with the specified image URI.
137 ///
138 /// This action updates software components by downloading and installing
139 /// a software image from the specified URI.
140 ///
141 /// # Arguments
142 ///
143 /// * `image_uri` - The URI of the software image to install
144 /// * `transfer_protocol` - Optional network protocol to use for retrieving the image
145 /// * `targets` - Optional list of URIs indicating where to apply the update
146 /// * `username` - Optional username for accessing the image URI
147 /// * `password` - Optional password for accessing the image URI
148 /// * `force_update` - Whether to bypass update policies (e.g., allow downgrade)
149 /// * `stage` - Whether to stage the image for later activation instead of immediate installation
150 /// * `local_image` - An indication of whether the service adds the image to the local image store
151 /// * `exclude_targets` - An array of URIs that indicate where not to apply the update image
152 ///
153 /// # Errors
154 ///
155 /// Returns an error if:
156 /// - The update service does not support the `SimpleUpdate` action
157 /// - The action execution fails
158 #[allow(clippy::too_many_arguments)]
159 pub async fn simple_update(
160 &self,
161 image_uri: String,
162 transfer_protocol: Option<TransferProtocolType>,
163 targets: Option<Vec<String>>,
164 username: Option<String>,
165 password: Option<String>,
166 force_update: Option<bool>,
167 stage: Option<bool>,
168 local_image: Option<bool>,
169 exclude_targets: Option<Vec<String>>,
170 ) -> Result<(), Error<B>>
171 where
172 B::Error: nv_redfish_core::ActionError,
173 {
174 let actions = self
175 .data
176 .actions
177 .as_ref()
178 .ok_or(Error::ActionNotAvailable)?;
179
180 actions
181 .simple_update(
182 self.bmc.as_ref(),
183 &UpdateServiceSimpleUpdateAction {
184 image_uri: Some(image_uri),
185 transfer_protocol,
186 targets,
187 username,
188 password,
189 force_update,
190 stage,
191 local_image,
192 exclude_targets,
193 },
194 )
195 .await
196 .map_err(Error::Bmc)?;
197
198 Ok(())
199 }
200
201 /// Start updates that have been previously invoked with an `OperationApplyTime` of `OnStartUpdateRequest`.
202 ///
203 /// # Errors
204 ///
205 /// Returns an error if:
206 /// - The update service does not support the `StartUpdate` action
207 /// - The action execution fails
208 pub async fn start_update(&self) -> Result<(), Error<B>>
209 where
210 B::Error: nv_redfish_core::ActionError,
211 {
212 let actions = self
213 .data
214 .actions
215 .as_ref()
216 .ok_or(Error::ActionNotAvailable)?;
217
218 actions
219 .start_update(self.bmc.as_ref())
220 .await
221 .map_err(Error::Bmc)?;
222
223 Ok(())
224 }
225}
226
227impl<B: Bmc> Resource for UpdateService<B> {
228 fn resource_ref(&self) -> &ResourceSchema {
229 &self.data.as_ref().base
230 }
231}
232
233// `ReleaseDate` is marked as `edm.DateTimeOffset`, but some systems
234// puts "00:00:00Z" as ReleaseDate that is not conform to ABNF of the DateTimeOffset.
235// we delete such fields...
236fn fw_inventory_patch_wrong_release_date(v: JsonValue) -> JsonValue {
237 if let JsonValue::Object(mut obj) = v {
238 if let Some(JsonValue::String(date)) = obj.get("ReleaseDate") {
239 if date == "00:00:00Z" {
240 obj.remove("ReleaseDate");
241 }
242 }
243 JsonValue::Object(obj)
244 } else {
245 v
246 }
247}