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::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}