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