Skip to main content

ironflow_cli/commands/
user.rs

1//! User subcommands: list, create, delete, set-role, groups, set-groups.
2
3use std::slice;
4
5use anyhow::{Context, Result};
6use clap::{ArgGroup, Args, Subcommand};
7use ironflow_sdk::IronflowClient;
8use ironflow_sdk::types::{CreateUserRequest, UpdateRoleRequest};
9use uuid::Uuid;
10
11use crate::confirm::{confirm, resolve_secret_value};
12use crate::output;
13
14/// Arguments for the `user` command group.
15#[derive(Debug, Args)]
16pub struct UserArgs {
17    /// User subcommand.
18    #[command(subcommand)]
19    pub command: UserCommands,
20}
21
22/// Available user subcommands.
23#[derive(Debug, Subcommand)]
24pub enum UserCommands {
25    /// List users.
26    List,
27    /// Create a user.
28    Create {
29        /// Display username.
30        username: String,
31        /// Email address.
32        #[arg(long)]
33        email: String,
34        /// Plaintext password: 12 to 128 characters, not a common password,
35        /// not containing the email or username. Read from stdin when
36        /// omitted, which keeps it out of the shell history.
37        #[arg(long)]
38        password: Option<String>,
39        /// Grant admin rights to the new user.
40        #[arg(long)]
41        admin: bool,
42    },
43    /// Delete a user.
44    Delete {
45        /// User UUID.
46        id: Uuid,
47        /// Skip the interactive confirmation.
48        #[arg(long)]
49        yes: bool,
50    },
51    /// Promote a user to admin or demote them to member.
52    #[command(group(ArgGroup::new("role").required(true).args(["admin", "member"])))]
53    SetRole {
54        /// User UUID.
55        id: Uuid,
56        /// Grant admin rights.
57        #[arg(long)]
58        admin: bool,
59        /// Revoke admin rights.
60        #[arg(long)]
61        member: bool,
62    },
63    /// Show the groups a user belongs to.
64    Groups {
65        /// User UUID.
66        id: Uuid,
67    },
68    /// Replace the groups a user belongs to.
69    ///
70    /// Group membership restricts who may vote on an approval gate whose
71    /// approvers list groups. Without any `--group`, the user leaves every
72    /// group.
73    SetGroups {
74        /// User UUID.
75        id: Uuid,
76        /// Group name. Repeat the flag for several groups.
77        #[arg(long = "group")]
78        groups: Vec<String>,
79    },
80}
81
82/// Execute a user subcommand.
83///
84/// # Errors
85///
86/// Returns an error on API failure, on an empty password, or when a
87/// destructive command is not confirmed.
88pub async fn execute(client: &IronflowClient, args: &UserArgs, json_mode: bool) -> Result<()> {
89    match &args.command {
90        UserCommands::List => {
91            let response = client.list_users().await?;
92            output::print_output(json_mode, &response, || output::users_table(&response.data))?;
93        }
94        UserCommands::Create {
95            username,
96            email,
97            password,
98            admin,
99        } => {
100            let password = resolve_secret_value(password.as_deref(), "password")?;
101            let request: CreateUserRequest = CreateUserRequest::builder()
102                .username(username.clone())
103                .email(email.clone())
104                .password(password)
105                .is_admin(*admin)
106                .try_into()
107                .context("failed to build CreateUserRequest")?;
108
109            let response = client.create_user(&request).await?;
110            output::print_output(json_mode, &response, || {
111                output::users_table(slice::from_ref(&response.data))
112            })?;
113        }
114        UserCommands::Delete { id, yes } => {
115            confirm(&format!("Delete user '{id}'?"), *yes)?;
116            client.delete_user(*id).await?;
117            output::report_deletion(json_mode, "user", id.to_string())?;
118        }
119        // `--admin` and `--member` are an exclusive, required clap group, so
120        // `admin` alone carries the whole decision.
121        UserCommands::SetRole { id, admin, .. } => {
122            let request: UpdateRoleRequest = UpdateRoleRequest::builder()
123                .is_admin(*admin)
124                .try_into()
125                .context("failed to build UpdateRoleRequest")?;
126
127            let response = client.update_role(*id, &request).await?;
128            output::print_output(json_mode, &response, || {
129                output::users_table(slice::from_ref(&response.data))
130            })?;
131        }
132        UserCommands::Groups { id } => {
133            let response = client.get_user_groups(*id).await?;
134            output::print_output(json_mode, &response, || {
135                output::user_groups_table(&response.data)
136            })?;
137        }
138        UserCommands::SetGroups { id, groups } => {
139            let response = client.update_user_groups(*id, groups).await?;
140            output::print_output(json_mode, &response, || {
141                output::user_groups_table(&response.data)
142            })?;
143        }
144    }
145    Ok(())
146}