mysqladm/client/commands/
edit_db_privs.rs1use std::collections::BTreeSet;
2
3use anyhow::Context;
4use clap::Parser;
5use dialoguer::{Confirm, Editor};
6use futures_util::SinkExt;
7use nix::unistd::{User, getuid};
8use tokio_stream::StreamExt;
9
10use crate::{
11 client::commands::erroneous_server_response,
12 core::{
13 database_privileges::{
14 DatabasePrivilegeEditEntry, DatabasePrivilegeRow, DatabasePrivilegeRowDiff,
15 DatabasePrivilegesDiff, create_or_modify_privilege_rows, diff_privileges,
16 display_privilege_diffs, generate_editor_content_from_privilege_data,
17 parse_privilege_data_from_editor_content, reduce_privilege_diffs,
18 },
19 protocol::{
20 ClientToServerMessageStream, Request, Response,
21 print_modify_database_privileges_output_status,
22 },
23 types::MySQLDatabase,
24 },
25};
26
27#[derive(Parser, Debug, Clone)]
28pub struct EditDbPrivsArgs {
29 pub name: Option<MySQLDatabase>,
31
32 #[arg(
33 short,
34 long,
35 value_name = "[DATABASE:]USER:[+-]PRIVILEGES",
36 num_args = 0..,
37 value_parser = DatabasePrivilegeEditEntry::parse_from_str,
38 )]
39 pub privs: Vec<DatabasePrivilegeEditEntry>,
40
41 #[arg(short, long)]
43 pub json: bool,
44
45 #[arg(short, long)]
47 pub editor: Option<String>,
48
49 #[arg(short, long)]
51 pub yes: bool,
52}
53
54pub async fn edit_database_privileges(
55 args: EditDbPrivsArgs,
56 mut server_connection: ClientToServerMessageStream,
57) -> anyhow::Result<()> {
58 let message = Request::ListPrivileges(args.name.to_owned().map(|name| vec![name]));
59
60 server_connection.send(message).await?;
61
62 let existing_privilege_rows = match server_connection.next().await {
63 Some(Ok(Response::ListPrivileges(databases))) => databases
64 .into_iter()
65 .filter_map(|(database_name, result)| match result {
66 Ok(privileges) => Some(privileges),
67 Err(err) => {
68 eprintln!("{}", err.to_error_message(&database_name));
69 eprintln!("Skipping...");
70 println!();
71 None
72 }
73 })
74 .flatten()
75 .collect::<Vec<_>>(),
76 Some(Ok(Response::ListAllPrivileges(privilege_rows))) => match privilege_rows {
77 Ok(list) => list,
78 Err(err) => {
79 server_connection.send(Request::Exit).await?;
80 return Err(anyhow::anyhow!(err.to_error_message())
81 .context("Failed to list database privileges"));
82 }
83 },
84 response => return erroneous_server_response(response),
85 };
86
87 let diffs: BTreeSet<DatabasePrivilegesDiff> = if !args.privs.is_empty() {
88 let privileges_to_change = parse_privilege_tables_from_args(&args)?;
89 create_or_modify_privilege_rows(&existing_privilege_rows, &privileges_to_change)?
90 } else {
91 let privileges_to_change =
92 edit_privileges_with_editor(&existing_privilege_rows, args.name.as_ref())?;
93 diff_privileges(&existing_privilege_rows, &privileges_to_change)
94 };
95 let diffs = reduce_privilege_diffs(&existing_privilege_rows, diffs)?;
96
97 if diffs.is_empty() {
98 println!("No changes to make.");
99 server_connection.send(Request::Exit).await?;
100 return Ok(());
101 }
102
103 println!("The following changes will be made:\n");
104 println!("{}", display_privilege_diffs(&diffs));
105
106 if !args.yes
107 && !Confirm::new()
108 .with_prompt("Do you want to apply these changes?")
109 .default(false)
110 .show_default(true)
111 .interact()?
112 {
113 server_connection.send(Request::Exit).await?;
114 return Ok(());
115 }
116
117 let message = Request::ModifyPrivileges(diffs);
118 server_connection.send(message).await?;
119
120 let result = match server_connection.next().await {
121 Some(Ok(Response::ModifyPrivileges(result))) => result,
122 response => return erroneous_server_response(response),
123 };
124
125 print_modify_database_privileges_output_status(&result);
126
127 server_connection.send(Request::Exit).await?;
128
129 Ok(())
130}
131
132fn parse_privilege_tables_from_args(
133 args: &EditDbPrivsArgs,
134) -> anyhow::Result<BTreeSet<DatabasePrivilegeRowDiff>> {
135 debug_assert!(!args.privs.is_empty());
136 args.privs
137 .iter()
138 .map(|priv_edit_entry| {
139 priv_edit_entry
140 .as_database_privileges_diff(args.name.as_ref())
141 .context(format!(
142 "Failed parsing database privileges: `{}`",
143 priv_edit_entry
144 ))
145 })
146 .collect::<anyhow::Result<BTreeSet<DatabasePrivilegeRowDiff>>>()
147}
148
149fn edit_privileges_with_editor(
150 privilege_data: &[DatabasePrivilegeRow],
151 database_name: Option<&MySQLDatabase>,
152) -> anyhow::Result<Vec<DatabasePrivilegeRow>> {
153 let unix_user = User::from_uid(getuid())
154 .context("Failed to look up your UNIX username")
155 .and_then(|u| u.ok_or(anyhow::anyhow!("Failed to look up your UNIX username")))?;
156
157 let editor_content =
158 generate_editor_content_from_privilege_data(privilege_data, &unix_user.name, database_name);
159
160 let result = Editor::new().extension("tsv").edit(&editor_content)?;
162
163 match result {
164 None => Ok(privilege_data.to_vec()),
165 Some(result) => parse_privilege_data_from_editor_content(result)
166 .context("Could not parse privilege data from editor"),
167 }
168}