Skip to main content

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::core::NavProperty;
24use crate::patch_support::Payload;
25use crate::patch_support::ReadPatchFn;
26use crate::schema::redfish::update_service::UpdateService as UpdateServiceSchema;
27use crate::schema::redfish::update_service::UpdateServiceSimpleUpdateAction;
28use crate::Error;
29use crate::NvBmc;
30use crate::Resource;
31use crate::ResourceSchema;
32use crate::ServiceRoot;
33use nv_redfish_core::Bmc;
34use nv_redfish_core::ModificationResponse;
35use serde_json::Value as JsonValue;
36use software_inventory::SoftwareInventoryCollection;
37use std::sync::Arc;
38
39#[doc(inline)]
40pub use crate::schema::redfish::update_service::TransferProtocolType;
41#[doc(inline)]
42pub use software_inventory::SoftwareInventory;
43#[doc(inline)]
44pub use software_inventory::Version;
45#[doc(inline)]
46pub use software_inventory::VersionRef;
47
48/// Update service.
49///
50/// Provides functions to access firmware and software inventory, and perform update actions.
51pub struct UpdateService<B: Bmc> {
52    bmc: NvBmc<B>,
53    data: Arc<UpdateServiceSchema>,
54    fw_inventory_read_patch_fn: Option<ReadPatchFn>,
55}
56
57impl<B: Bmc> UpdateService<B> {
58    /// Create a new update service handle.
59    pub(crate) async fn new(
60        bmc: &NvBmc<B>,
61        root: &ServiceRoot<B>,
62    ) -> Result<Option<Self>, Error<B>> {
63        let mut service_patches = Vec::new();
64        if bmc.quirks.bug_missing_update_service_name_field() {
65            service_patches.push(add_default_update_service_name);
66        }
67        let service_patch_fn = (!service_patches.is_empty()).then(|| {
68            Arc::new(move |v| service_patches.iter().fold(v, |acc, f| f(acc))) as ReadPatchFn
69        });
70
71        let mut fw_inventory_patches = Vec::new();
72        if bmc.quirks.fw_inventory_wrong_release_date() {
73            fw_inventory_patches.push(fw_inventory_patch_wrong_release_date);
74        }
75        let fw_inventory_read_patch_fn = (!fw_inventory_patches.is_empty()).then(|| {
76            Arc::new(move |v| fw_inventory_patches.iter().fold(v, |acc, f| f(acc))) as ReadPatchFn
77        });
78
79        if let Some(nav) = &root.root.update_service {
80            if let Some(service_patch_fn) = service_patch_fn {
81                Payload::get(bmc.as_ref(), nav, service_patch_fn.as_ref()).await
82            } else {
83                nav.get(bmc.as_ref()).await.map_err(Error::Bmc)
84            }
85            .map(Some)
86        } else if bmc.quirks.bug_missing_root_nav_properties() {
87            let nav =
88                NavProperty::new_reference(format!("{}/UpdateService", root.odata_id()).into());
89            if let Some(service_patch_fn) = service_patch_fn {
90                Payload::get(bmc.as_ref(), &nav, service_patch_fn.as_ref()).await
91            } else {
92                nav.get(bmc.as_ref()).await.map_err(Error::Bmc)
93            }
94            .map(Some)
95        } else {
96            Ok(None)
97        }
98        .map(|d| {
99            d.map(|data| Self {
100                bmc: bmc.clone(),
101                data,
102                fw_inventory_read_patch_fn,
103            })
104        })
105    }
106
107    /// Get the raw schema data for this update service.
108    ///
109    /// Returns an `Arc` to the underlying schema, allowing cheap cloning
110    /// and sharing of the data.
111    #[must_use]
112    pub fn raw(&self) -> Arc<UpdateServiceSchema> {
113        self.data.clone()
114    }
115
116    /// List all firmware inventory items.
117    ///
118    /// # Errors
119    ///
120    /// Returns an error if:
121    /// - The update service does not have a firmware inventory collection
122    /// - Fetching firmware inventory data fails
123    pub async fn firmware_inventories(
124        &self,
125    ) -> Result<Option<Vec<SoftwareInventory<B>>>, Error<B>> {
126        if let Some(collection_ref) = &self.data.firmware_inventory {
127            SoftwareInventoryCollection::new(
128                &self.bmc,
129                collection_ref,
130                self.fw_inventory_read_patch_fn.clone(),
131            )
132            .await?
133            .members()
134            .await
135            .map(Some)
136        } else {
137            Ok(None)
138        }
139    }
140
141    /// List all software inventory items.
142    ///
143    /// # Errors
144    ///
145    /// Returns an error if:
146    /// - The update service does not have a software inventory collection
147    /// - Fetching software inventory data fails
148    pub async fn software_inventories(
149        &self,
150    ) -> Result<Option<Vec<SoftwareInventory<B>>>, Error<B>> {
151        if let Some(collection_ref) = &self.data.software_inventory {
152            let collection = self.bmc.expand_property(collection_ref).await?;
153            let mut items = Vec::new();
154            for item_ref in &collection.members {
155                items.push(SoftwareInventory::new(&self.bmc, item_ref, None).await?);
156            }
157            Ok(Some(items))
158        } else {
159            Ok(None)
160        }
161    }
162
163    /// Perform a simple update with the specified image URI.
164    ///
165    /// This action updates software components by downloading and installing
166    /// a software image from the specified URI.
167    ///
168    /// # Arguments
169    ///
170    /// * `image_uri` - The URI of the software image to install
171    /// * `transfer_protocol` - Optional network protocol to use for retrieving the image
172    /// * `targets` - Optional list of URIs indicating where to apply the update
173    /// * `username` - Optional username for accessing the image URI
174    /// * `password` - Optional password for accessing the image URI
175    /// * `force_update` - Whether to bypass update policies (e.g., allow downgrade)
176    /// * `stage` - Whether to stage the image for later activation instead of immediate installation
177    /// * `local_image` - An indication of whether the service adds the image to the local image store
178    /// * `exclude_targets` - An array of URIs that indicate where not to apply the update image
179    ///
180    /// # Errors
181    ///
182    /// Returns an error if:
183    /// - The update service does not support the `SimpleUpdate` action
184    /// - The action execution fails
185    #[allow(clippy::too_many_arguments)]
186    pub async fn simple_update(
187        &self,
188        image_uri: String,
189        transfer_protocol: Option<TransferProtocolType>,
190        targets: Option<Vec<String>>,
191        username: Option<String>,
192        password: Option<String>,
193        force_update: Option<bool>,
194        stage: Option<bool>,
195        local_image: Option<bool>,
196        exclude_targets: Option<Vec<String>>,
197    ) -> Result<ModificationResponse<()>, Error<B>>
198    where
199        B::Error: nv_redfish_core::ActionError,
200    {
201        let actions = self
202            .data
203            .actions
204            .as_ref()
205            .ok_or(Error::ActionNotAvailable)?;
206
207        actions
208            .simple_update(
209                self.bmc.as_ref(),
210                &UpdateServiceSimpleUpdateAction {
211                    image_uri: Some(image_uri),
212                    transfer_protocol,
213                    targets,
214                    username,
215                    password,
216                    force_update,
217                    stage,
218                    local_image,
219                    exclude_targets,
220                },
221            )
222            .await
223            .map_err(Error::Bmc)
224    }
225
226    /// Start updates that have been previously invoked with an `OperationApplyTime` of `OnStartUpdateRequest`.
227    ///
228    /// # Errors
229    ///
230    /// Returns an error if:
231    /// - The update service does not support the `StartUpdate` action
232    /// - The action execution fails
233    pub async fn start_update(&self) -> Result<ModificationResponse<()>, Error<B>>
234    where
235        B::Error: nv_redfish_core::ActionError,
236    {
237        let actions = self
238            .data
239            .actions
240            .as_ref()
241            .ok_or(Error::ActionNotAvailable)?;
242
243        actions
244            .start_update(self.bmc.as_ref())
245            .await
246            .map_err(Error::Bmc)
247    }
248}
249
250impl<B: Bmc> Resource for UpdateService<B> {
251    fn resource_ref(&self) -> &ResourceSchema {
252        &self.data.as_ref().base
253    }
254}
255
256// `ReleaseDate` is marked as `edm.DateTimeOffset`, but some systems
257// puts "00:00:00Z" as ReleaseDate that is not conform to ABNF of the DateTimeOffset.
258// we delete such fields...
259fn fw_inventory_patch_wrong_release_date(v: JsonValue) -> JsonValue {
260    if let JsonValue::Object(mut obj) = v {
261        if let Some(JsonValue::String(date)) = obj.get("ReleaseDate") {
262            if date == "00:00:00Z" {
263                obj.remove("ReleaseDate");
264            }
265        }
266        JsonValue::Object(obj)
267    } else {
268        v
269    }
270}
271
272fn add_default_update_service_name(v: JsonValue) -> JsonValue {
273    if let JsonValue::Object(mut obj) = v {
274        obj.entry("Name")
275            .or_insert(JsonValue::String("Unnamed update service".into()));
276        JsonValue::Object(obj)
277    } else {
278        v
279    }
280}