Skip to main content

nv_redfish/account/
item.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//! Redfish ManagerAccount — high-level wrapper
17//!
18//! Provides `Account`, an ergonomic handle over a Redfish `ManagerAccount`:
19//! - Read raw data with `raw()`
20//! - Update fields via `update()`, or use helpers `update_password()` and
21//!   `update_user_name()`
22//! - Delete the account with `delete()`; optionally disable instead of deleting
23//!   when configured
24//!
25//! Configuration:
26//! - `Config::read_patch_fn`: apply read-time JSON patches for vendor
27//!   compatibility
28//! - `Config::disable_account_on_delete`: make `delete()` disable the account
29//!   rather than remove it
30//!
31//! Note: `Account` objects are created by higher-level APIs (e.g.
32//! `AccountCollection`) and do not create accounts on the BMC by themselves.
33//! Use the collection to create new accounts.
34
35use crate::account::ManagerAccountUpdate;
36use crate::patch_support::Payload;
37use crate::patch_support::ReadPatchFn;
38use crate::patch_support::UpdateWithPatch;
39use crate::schema::manager_account::ManagerAccount;
40use crate::Error;
41use crate::NvBmc;
42use crate::Resource;
43use crate::ResourceSchema;
44use nv_redfish_core::Bmc;
45use nv_redfish_core::EntityTypeRef as _;
46use nv_redfish_core::ModificationResponse;
47use nv_redfish_core::NavProperty;
48use std::convert::identity;
49use std::sync::Arc;
50
51#[derive(Clone)]
52pub struct Config {
53    /// Function to patch input JSON when reading account structures.
54    pub read_patch_fn: Option<ReadPatchFn>,
55    /// If true, deletion disables the account instead of removing it.
56    pub disable_account_on_delete: bool,
57}
58
59/// Represents a Redfish `ManagerAccount`.
60pub struct Account<B: Bmc> {
61    config: Config,
62    bmc: NvBmc<B>,
63    data: Arc<ManagerAccount>,
64}
65
66impl<B: Bmc> UpdateWithPatch<ManagerAccount, ManagerAccountUpdate, B> for Account<B> {
67    fn entity_ref(&self) -> &ManagerAccount {
68        self.data.as_ref()
69    }
70    fn patch(&self) -> Option<&ReadPatchFn> {
71        self.config.read_patch_fn.as_ref()
72    }
73    fn bmc(&self) -> &B {
74        self.bmc.as_ref()
75    }
76}
77
78impl<B: Bmc> Account<B> {
79    /// Create a new account handle. This does not create an account on the
80    /// BMC.
81    pub(crate) async fn new(
82        bmc: &NvBmc<B>,
83        nav: &NavProperty<ManagerAccount>,
84        config: &Config,
85    ) -> Result<Self, Error<B>> {
86        if let Some(read_patch_fn) = &config.read_patch_fn {
87            Payload::get(bmc.as_ref(), nav, read_patch_fn.as_ref()).await
88        } else {
89            nav.get(bmc.as_ref()).await.map_err(Error::Bmc)
90        }
91        .map(|data| Self {
92            bmc: bmc.clone(),
93            data,
94            config: config.clone(),
95        })
96    }
97
98    /// Create from existing data.
99    pub(crate) fn from_data(bmc: NvBmc<B>, data: ManagerAccount, config: Config) -> Self {
100        Self {
101            bmc,
102            data: Arc::new(data),
103            config,
104        }
105    }
106
107    /// Raw `ManagerAccount` data.
108    #[must_use]
109    pub fn raw(&self) -> Arc<ManagerAccount> {
110        self.data.clone()
111    }
112
113    /// Account is enabled.
114    #[must_use]
115    pub fn is_enabled(&self) -> bool {
116        self.data.enabled.is_none_or(identity)
117    }
118
119    /// Update the account.
120    ///
121    /// Returns one of the following modification outcomes:
122    ///
123    /// - `ModificationResponse::Entity` contains the updated account.
124    /// - `ModificationResponse::Task` identifies an asynchronous operation.
125    /// - `ModificationResponse::Empty` reports synchronous success without a
126    ///   response body.
127    ///
128    /// # Errors
129    ///
130    /// Returns an error if the server responds with an error or if the
131    /// response cannot be parsed.
132    pub async fn update(
133        &self,
134        update: &ManagerAccountUpdate,
135    ) -> Result<ModificationResponse<Self>, Error<B>> {
136        Ok(self
137            .update_with_patch(update)
138            .await?
139            .map_entity(|ma| Self::from_data(self.bmc.clone(), ma, self.config.clone())))
140    }
141
142    /// Update the account's password.
143    ///
144    /// Returns one of the following modification outcomes:
145    ///
146    /// - `ModificationResponse::Entity` contains the updated account.
147    /// - `ModificationResponse::Task` identifies an asynchronous operation.
148    /// - `ModificationResponse::Empty` reports synchronous success without a
149    ///   response body.
150    ///
151    /// # Errors
152    ///
153    /// Returns an error if the server responds with an error or if the
154    /// response cannot be parsed.
155    pub async fn update_password(
156        &self,
157        password: String,
158    ) -> Result<ModificationResponse<Self>, Error<B>> {
159        self.update(
160            &ManagerAccountUpdate::builder()
161                .with_password(password)
162                .build(),
163        )
164        .await
165    }
166
167    /// Update the account's user name.
168    ///
169    /// Returns one of the following modification outcomes:
170    ///
171    /// - `ModificationResponse::Entity` contains the updated account.
172    /// - `ModificationResponse::Task` identifies an asynchronous operation.
173    /// - `ModificationResponse::Empty` reports synchronous success without a
174    ///   response body.
175    ///
176    /// # Errors
177    ///
178    /// Returns an error if the server responds with an error or if the
179    /// response cannot be parsed.
180    pub async fn update_user_name(
181        &self,
182        user_name: String,
183    ) -> Result<ModificationResponse<Self>, Error<B>> {
184        self.update(
185            &ManagerAccountUpdate::builder()
186                .with_user_name(user_name)
187                .build(),
188        )
189        .await
190    }
191
192    /// Delete the current account.
193    ///
194    /// Returns one of the following modification outcomes:
195    ///
196    /// - `ModificationResponse::Entity` contains the account returned by the
197    ///   server. When deletion is configured to disable the account, this is the
198    ///   updated account.
199    /// - `ModificationResponse::Task` identifies an asynchronous operation.
200    /// - `ModificationResponse::Empty` reports synchronous success without a
201    ///   response body.
202    ///
203    /// # Errors
204    ///
205    /// Returns an error if deletion fails.
206    pub async fn delete(&self) -> Result<ModificationResponse<Self>, Error<B>> {
207        if self.config.disable_account_on_delete {
208            self.update(&ManagerAccountUpdate::builder().with_enabled(false).build())
209                .await
210        } else {
211            self.bmc
212                .as_ref()
213                .delete::<NavProperty<ManagerAccount>>(self.data.odata_id())
214                .await
215                .map_err(Error::Bmc)?
216                .try_map_entity_async(|nav| async move {
217                    Self::new(&self.bmc, &nav, &self.config).await
218                })
219                .await
220        }
221    }
222}
223
224impl<B: Bmc> Resource for Account<B> {
225    fn resource_ref(&self) -> &ResourceSchema {
226        &self.data.as_ref().base
227    }
228}