Skip to main content

postgresql_commands/
createuser.rs

1use crate::Settings;
2use crate::traits::CommandBuilder;
3use std::convert::AsRef;
4use std::ffi::{OsStr, OsString};
5use std::path::PathBuf;
6
7/// `createuser` creates a new `PostgreSQL` role.
8#[derive(Clone, Debug, Default)]
9pub struct CreateUserBuilder {
10    program_dir: Option<PathBuf>,
11    envs: Vec<(OsString, OsString)>,
12    with_admin: Option<OsString>,
13    connection_limit: Option<u32>,
14    createdb: bool,
15    no_createdb: bool,
16    echo: bool,
17    member_of: Option<OsString>,
18    inherit: bool,
19    no_inherit: bool,
20    login: bool,
21    no_login: bool,
22    with_member: Option<OsString>,
23    pwprompt: bool,
24    createrole: bool,
25    no_createrole: bool,
26    superuser: bool,
27    no_superuser: bool,
28    valid_until: Option<OsString>,
29    version: bool,
30    interactive: bool,
31    bypassrls: bool,
32    no_bypassrls: bool,
33    replication: bool,
34    no_replication: bool,
35    help: bool,
36    host: Option<OsString>,
37    port: Option<u16>,
38    username: Option<OsString>,
39    no_password: bool,
40    password: bool,
41    pg_password: Option<OsString>,
42}
43
44impl CreateUserBuilder {
45    /// Create a new [`CreateUserBuilder`]
46    #[must_use]
47    pub fn new() -> Self {
48        Self::default()
49    }
50
51    /// Create a new [`CreateUserBuilder`] from [Settings]
52    pub fn from(settings: &dyn Settings) -> Self {
53        let mut builder = Self::new()
54            .program_dir(settings.get_binary_dir())
55            .host(settings.get_host())
56            .port(settings.get_port())
57            .username(settings.get_username())
58            .pg_password(settings.get_password());
59        if let Some(socket_dir) = settings.get_socket_dir() {
60            builder = builder.host(socket_dir.to_string_lossy().to_string());
61        }
62        builder
63    }
64
65    /// Location of the program binary
66    #[must_use]
67    pub fn program_dir<P: Into<PathBuf>>(mut self, path: P) -> Self {
68        self.program_dir = Some(path.into());
69        self
70    }
71
72    /// ROLE will be a member of new role with admin option
73    #[must_use]
74    pub fn with_admin<S: AsRef<OsStr>>(mut self, role: S) -> Self {
75        self.with_admin = Some(role.as_ref().to_os_string());
76        self
77    }
78
79    /// Connection limit for role (default: no limit)
80    #[must_use]
81    pub fn connection_limit(mut self, limit: u32) -> Self {
82        self.connection_limit = Some(limit);
83        self
84    }
85
86    /// Role can create new databases
87    #[must_use]
88    pub fn createdb(mut self) -> Self {
89        self.createdb = true;
90        self
91    }
92
93    /// Role cannot create databases (default)
94    #[must_use]
95    pub fn no_createdb(mut self) -> Self {
96        self.no_createdb = true;
97        self
98    }
99
100    /// Show the commands being sent to the server
101    #[must_use]
102    pub fn echo(mut self) -> Self {
103        self.echo = true;
104        self
105    }
106
107    /// New role will be a member of ROLE
108    #[must_use]
109    pub fn member_of<S: AsRef<OsStr>>(mut self, role: S) -> Self {
110        self.member_of = Some(role.as_ref().to_os_string());
111        self
112    }
113
114    /// Role inherits privileges of roles it is a member of (default)
115    #[must_use]
116    pub fn inherit(mut self) -> Self {
117        self.inherit = true;
118        self
119    }
120
121    /// Role does not inherit privileges
122    #[must_use]
123    pub fn no_inherit(mut self) -> Self {
124        self.no_inherit = true;
125        self
126    }
127
128    /// Role can login (default)
129    #[must_use]
130    pub fn login(mut self) -> Self {
131        self.login = true;
132        self
133    }
134
135    /// Role cannot login
136    #[must_use]
137    pub fn no_login(mut self) -> Self {
138        self.no_login = true;
139        self
140    }
141
142    /// ROLE will be a member of new role
143    #[must_use]
144    pub fn with_member<S: AsRef<OsStr>>(mut self, role: S) -> Self {
145        self.with_member = Some(role.as_ref().to_os_string());
146        self
147    }
148
149    /// Assign a password to new role
150    #[must_use]
151    pub fn pwprompt(mut self) -> Self {
152        self.pwprompt = true;
153        self
154    }
155
156    /// Role can create new roles
157    #[must_use]
158    pub fn createrole(mut self) -> Self {
159        self.createrole = true;
160        self
161    }
162
163    /// Role cannot create roles (default)
164    #[must_use]
165    pub fn no_createrole(mut self) -> Self {
166        self.no_createrole = true;
167        self
168    }
169
170    /// Role will be superuser
171    #[must_use]
172    pub fn superuser(mut self) -> Self {
173        self.superuser = true;
174        self
175    }
176
177    /// Role will not be superuser (default)
178    #[must_use]
179    pub fn no_superuser(mut self) -> Self {
180        self.no_superuser = true;
181        self
182    }
183
184    /// Password expiration date and time for role
185    #[must_use]
186    pub fn valid_until<S: AsRef<OsStr>>(mut self, timestamp: S) -> Self {
187        self.valid_until = Some(timestamp.as_ref().to_os_string());
188        self
189    }
190
191    /// Output version information, then exit
192    #[must_use]
193    pub fn version(mut self) -> Self {
194        self.version = true;
195        self
196    }
197
198    /// Prompt for missing role name and attributes rather than using defaults
199    #[must_use]
200    pub fn interactive(mut self) -> Self {
201        self.interactive = true;
202        self
203    }
204
205    /// Role can bypass row-level security (RLS) policy
206    #[must_use]
207    pub fn bypassrls(mut self) -> Self {
208        self.bypassrls = true;
209        self
210    }
211
212    /// Role cannot bypass row-level security (RLS) policy (default)
213    #[must_use]
214    pub fn no_bypassrls(mut self) -> Self {
215        self.no_bypassrls = true;
216        self
217    }
218
219    /// Role can initiate replication
220    #[must_use]
221    pub fn replication(mut self) -> Self {
222        self.replication = true;
223        self
224    }
225
226    /// Role cannot initiate replication (default)
227    #[must_use]
228    pub fn no_replication(mut self) -> Self {
229        self.no_replication = true;
230        self
231    }
232
233    /// Show this help, then exit
234    #[must_use]
235    pub fn help(mut self) -> Self {
236        self.help = true;
237        self
238    }
239
240    /// Database server host or socket directory
241    #[must_use]
242    pub fn host<S: AsRef<OsStr>>(mut self, host: S) -> Self {
243        self.host = Some(host.as_ref().to_os_string());
244        self
245    }
246
247    /// Database server port
248    #[must_use]
249    pub fn port(mut self, port: u16) -> Self {
250        self.port = Some(port);
251        self
252    }
253
254    /// User name to connect as (not the one to create)
255    #[must_use]
256    pub fn username<S: AsRef<OsStr>>(mut self, username: S) -> Self {
257        self.username = Some(username.as_ref().to_os_string());
258        self
259    }
260
261    /// Never prompt for password
262    #[must_use]
263    pub fn no_password(mut self) -> Self {
264        self.no_password = true;
265        self
266    }
267
268    /// Force password prompt
269    #[must_use]
270    pub fn password(mut self) -> Self {
271        self.password = true;
272        self
273    }
274
275    /// user password
276    #[must_use]
277    pub fn pg_password<S: AsRef<OsStr>>(mut self, pg_password: S) -> Self {
278        self.pg_password = Some(pg_password.as_ref().to_os_string());
279        self
280    }
281}
282
283impl CommandBuilder for CreateUserBuilder {
284    /// Get the program name
285    fn get_program(&self) -> &'static OsStr {
286        "createuser".as_ref()
287    }
288
289    /// Location of the program binary
290    fn get_program_dir(&self) -> &Option<PathBuf> {
291        &self.program_dir
292    }
293
294    /// Get the arguments for the command
295    fn get_args(&self) -> Vec<OsString> {
296        let mut args: Vec<OsString> = Vec::new();
297
298        if let Some(role) = &self.with_admin {
299            args.push("--with-admin".into());
300            args.push(role.into());
301        }
302
303        if let Some(limit) = &self.connection_limit {
304            args.push("--connection-limit".into());
305            args.push(limit.to_string().into());
306        }
307
308        if self.createdb {
309            args.push("--createdb".into());
310        }
311
312        if self.no_createdb {
313            args.push("--no-createdb".into());
314        }
315
316        if self.echo {
317            args.push("--echo".into());
318        }
319
320        if let Some(role) = &self.member_of {
321            args.push("--member-of".into());
322            args.push(role.into());
323        }
324
325        if self.inherit {
326            args.push("--inherit".into());
327        }
328
329        if self.no_inherit {
330            args.push("--no-inherit".into());
331        }
332
333        if self.login {
334            args.push("--login".into());
335        }
336
337        if self.no_login {
338            args.push("--no-login".into());
339        }
340
341        if let Some(role) = &self.with_member {
342            args.push("--with-member".into());
343            args.push(role.into());
344        }
345
346        if self.pwprompt {
347            args.push("--pwprompt".into());
348        }
349
350        if self.createrole {
351            args.push("--createrole".into());
352        }
353
354        if self.no_createrole {
355            args.push("--no-createrole".into());
356        }
357
358        if self.superuser {
359            args.push("--superuser".into());
360        }
361
362        if self.no_superuser {
363            args.push("--no-superuser".into());
364        }
365
366        if let Some(timestamp) = &self.valid_until {
367            args.push("--valid-until".into());
368            args.push(timestamp.into());
369        }
370
371        if self.version {
372            args.push("--version".into());
373        }
374
375        if self.interactive {
376            args.push("--interactive".into());
377        }
378
379        if self.bypassrls {
380            args.push("--bypassrls".into());
381        }
382
383        if self.no_bypassrls {
384            args.push("--no-bypassrls".into());
385        }
386
387        if self.replication {
388            args.push("--replication".into());
389        }
390
391        if self.no_replication {
392            args.push("--no-replication".into());
393        }
394
395        if self.help {
396            args.push("--help".into());
397        }
398
399        if let Some(host) = &self.host {
400            args.push("--host".into());
401            args.push(host.into());
402        }
403
404        if let Some(port) = &self.port {
405            args.push("--port".into());
406            args.push(port.to_string().into());
407        }
408
409        if let Some(username) = &self.username {
410            args.push("--username".into());
411            args.push(username.into());
412        }
413
414        if self.no_password {
415            args.push("--no-password".into());
416        }
417
418        if self.password {
419            args.push("--password".into());
420        }
421
422        args
423    }
424
425    /// Get the environment variables for the command
426    fn get_envs(&self) -> Vec<(OsString, OsString)> {
427        let mut envs: Vec<(OsString, OsString)> = self.envs.clone();
428
429        if let Some(password) = &self.pg_password {
430            envs.push(("PGPASSWORD".into(), password.into()));
431        }
432
433        envs
434    }
435
436    /// Set an environment variable for the command
437    fn env<S: AsRef<OsStr>>(mut self, key: S, value: S) -> Self {
438        self.envs
439            .push((key.as_ref().to_os_string(), value.as_ref().to_os_string()));
440        self
441    }
442}
443
444#[cfg(test)]
445mod tests {
446    use super::*;
447    use crate::TestSettings;
448    use crate::TestSocketSettings;
449    use crate::traits::CommandToString;
450    use test_log::test;
451
452    #[test]
453    fn test_builder_new() {
454        let command = CreateUserBuilder::new().program_dir(".").build();
455        assert_eq!(
456            PathBuf::from(".").join("createuser"),
457            PathBuf::from(command.to_command_string().replace('"', ""))
458        );
459    }
460
461    #[test]
462    fn test_builder_from() {
463        let command = CreateUserBuilder::from(&TestSettings).build();
464        #[cfg(not(target_os = "windows"))]
465        let command_prefix = r#"PGPASSWORD="password" "./createuser" "#;
466        #[cfg(target_os = "windows")]
467        let command_prefix = r#"".\\createuser" "#;
468
469        assert_eq!(
470            format!(
471                r#"{command_prefix}"--host" "localhost" "--port" "5432" "--username" "postgres""#
472            ),
473            command.to_command_string()
474        );
475    }
476
477    #[test]
478    fn test_builder() {
479        let command = CreateUserBuilder::new()
480            .env("PGDATABASE", "database")
481            .with_admin("admin")
482            .connection_limit(10)
483            .createdb()
484            .no_createdb()
485            .echo()
486            .member_of("member")
487            .inherit()
488            .no_inherit()
489            .login()
490            .no_login()
491            .with_member("member")
492            .pwprompt()
493            .createrole()
494            .no_createrole()
495            .superuser()
496            .no_superuser()
497            .valid_until("2021-12-31")
498            .version()
499            .interactive()
500            .bypassrls()
501            .no_bypassrls()
502            .replication()
503            .no_replication()
504            .help()
505            .host("localhost")
506            .port(5432)
507            .username("username")
508            .no_password()
509            .password()
510            .pg_password("password")
511            .build();
512        #[cfg(not(target_os = "windows"))]
513        let command_prefix = r#"PGDATABASE="database" PGPASSWORD="password" "#;
514        #[cfg(target_os = "windows")]
515        let command_prefix = String::new();
516
517        assert_eq!(
518            format!(
519                r#"{command_prefix}"createuser" "--with-admin" "admin" "--connection-limit" "10" "--createdb" "--no-createdb" "--echo" "--member-of" "member" "--inherit" "--no-inherit" "--login" "--no-login" "--with-member" "member" "--pwprompt" "--createrole" "--no-createrole" "--superuser" "--no-superuser" "--valid-until" "2021-12-31" "--version" "--interactive" "--bypassrls" "--no-bypassrls" "--replication" "--no-replication" "--help" "--host" "localhost" "--port" "5432" "--username" "username" "--no-password" "--password""#
520            ),
521            command.to_command_string()
522        );
523    }
524
525    #[test]
526    fn test_builder_from_socket() {
527        let command = CreateUserBuilder::from(&TestSocketSettings).build();
528        #[cfg(not(target_os = "windows"))]
529        let command_prefix = r#"PGPASSWORD="password" "./createuser" "#;
530        #[cfg(target_os = "windows")]
531        let command_prefix = r#"".\\createuser" "#;
532
533        assert_eq!(
534            format!(
535                r#"{command_prefix}"--host" "/tmp/pg_socket" "--port" "5432" "--username" "postgres""#
536            ),
537            command.to_command_string()
538        );
539    }
540}