1use std::io::Write;
5use std::process::Command;
6use std::process::Stdio;
7
8pub trait Distribution {
9 fn create_user(
10 &self,
11 username: &str,
12 password: &str,
13 ) -> Result<i32, String>;
14 fn set_hostname(&self, hostname: &str) -> Result<i32, String>;
15}
16
17pub enum Distributions {
18 Debian,
19 Ubuntu,
20}
21
22impl Distribution for Distributions {
23 fn create_user(
24 &self,
25 username: &str,
26 password: &str,
27 ) -> Result<i32, String> {
28 match self {
29 Distributions::Debian | Distributions::Ubuntu => {
30 let mut home_path = "/home/".to_string();
31 home_path.push_str(username);
32
33 match Command::new("useradd")
34 .arg(username)
35 .arg("--comment")
36 .arg(
37 "Provisioning agent created this user based on username provided in IMDS",
38 )
39 .arg("--groups")
40 .arg("adm,audio,cdrom,dialout,dip,floppy,lxd,netdev,plugdev,sudo,video")
41 .arg("-d")
42 .arg(home_path.clone())
43 .arg("-m")
44 .status(){
45 Ok(_)=>(),
46 Err(err) => return Err(err.to_string()),
47 };
48
49 if password.is_empty() {
50 match Command::new("passwd")
51 .arg("-d")
52 .arg(username)
53 .status()
54 {
55 Ok(status_code) => {
56 if !status_code.success() {
57 return Err("Failed to create user".to_string());
58 }
59 }
60 Err(err) => return Err(err.to_string()),
61 };
62 } else {
63 let input = format!("{}:{}", username, password);
64
65 let mut output = Command::new("chpasswd")
66 .stdin(Stdio::piped())
67 .stdout(Stdio::null())
68 .stderr(Stdio::inherit())
69 .spawn()
70 .expect("Failed to run chpasswd.");
71
72 let mut stdin =
73 output.stdin.as_ref().ok_or("Failed to open stdin")?;
74
75 stdin.write_all(input.as_bytes()).map_err(|error| {
76 format!("Failed to write to stdin: {}", error)
77 })?;
78
79 let status = output.wait().map_err(|error| {
80 format!("Failed to wait for stdin command: {}", error)
81 })?;
82
83 if !status.success() {
84 return Err(format!(
85 "Chpasswd command failed with exit code {}",
86 status.code().unwrap_or(-1)
87 ));
88 }
89 }
90
91 Ok(0)
92 }
93 }
94 }
95 fn set_hostname(&self, hostname: &str) -> Result<i32, String> {
96 match self {
97 Distributions::Debian | Distributions::Ubuntu => {
98 match Command::new("hostnamectl")
99 .arg("set-hostname")
100 .arg(hostname)
101 .status()
102 {
103 Ok(status_code) => Ok(status_code.code().unwrap_or(1)),
104 Err(err) => Err(err.to_string()),
105 }
106 }
107 }
108 }
109}
110impl From<&str> for Distributions {
111 fn from(s: &str) -> Self {
112 match s {
113 "debian" => Distributions::Debian,
114 "ubuntu" => Distributions::Ubuntu,
115 _ => panic!("Unknown distribution"),
116 }
117 }
118}