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::redfish::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 the new (updated) account.
122    ///
123    /// # Errors
124    ///
125    /// Returns an error if the server responds with an error or if the
126    /// response cannot be parsed.
127    pub async fn update(&self, update: &ManagerAccountUpdate) -> Result<Option<Self>, Error<B>> {
128        match self.update_with_patch(update).await? {
129            ModificationResponse::Entity(ma) => Ok(Some(Self::from_data(
130                self.bmc.clone(),
131                ma,
132                self.config.clone(),
133            ))),
134            ModificationResponse::Task(_) | ModificationResponse::Empty => Ok(None),
135        }
136    }
137
138    /// Update the account's password.
139    ///
140    /// Returns the new (updated) account.
141    ///
142    /// # Errors
143    ///
144    /// Returns an error if the server responds with an error or if the
145    /// response cannot be parsed.
146    pub async fn update_password(&self, password: String) -> Result<Option<Self>, Error<B>> {
147        self.update(
148            &ManagerAccountUpdate::builder()
149                .with_password(password)
150                .build(),
151        )
152        .await
153    }
154
155    /// Update the account's user name.
156    ///
157    /// Returns the new (updated) account.
158    ///
159    /// # Errors
160    ///
161    /// Returns an error if the server responds with an error or if the
162    /// response cannot be parsed.
163    pub async fn update_user_name(&self, user_name: String) -> Result<Option<Self>, Error<B>> {
164        self.update(
165            &ManagerAccountUpdate::builder()
166                .with_user_name(user_name)
167                .build(),
168        )
169        .await
170    }
171
172    /// Delete the current account.
173    ///
174    /// # Errors
175    ///
176    /// Returns an error if deletion fails.
177    pub async fn delete(&self) -> Result<Option<Self>, Error<B>> {
178        if self.config.disable_account_on_delete {
179            self.update(&ManagerAccountUpdate::builder().with_enabled(false).build())
180                .await
181        } else {
182            match self
183                .bmc
184                .as_ref()
185                .delete::<NavProperty<ManagerAccount>>(self.data.odata_id())
186                .await
187                .map_err(Error::Bmc)?
188            {
189                ModificationResponse::Entity(nav) => {
190                    Self::new(&self.bmc, &nav, &self.config).await.map(Some)
191                }
192                ModificationResponse::Task(_) | ModificationResponse::Empty => Ok(None),
193            }
194        }
195    }
196}
197
198impl<B: Bmc> Resource for Account<B> {
199    fn resource_ref(&self) -> &ResourceSchema {
200        &self.data.as_ref().base
201    }
202}