comtrya_lib/atoms/file/
chown.rs

1use crate::atoms::Outcome;
2
3use super::super::Atom;
4use super::FileAtom;
5use std::path::PathBuf;
6
7#[cfg(unix)]
8use tracing::error;
9
10#[derive(Debug)]
11pub struct Chown {
12    pub path: PathBuf,
13    pub owner: String,
14    pub group: String,
15}
16
17impl FileAtom for Chown {
18    fn get_path(&self) -> &PathBuf {
19        &self.path
20    }
21}
22
23impl std::fmt::Display for Chown {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        write!(
26            f,
27            "The owner and group on {} need to be set to {}:{}",
28            self.path.display(),
29            self.owner,
30            self.group,
31        )
32    }
33}
34
35#[cfg(unix)]
36use std::os::unix::prelude::MetadataExt;
37
38#[cfg(unix)]
39use file_owner::PathExt;
40
41#[cfg(unix)]
42impl Atom for Chown {
43    fn plan(&self) -> anyhow::Result<Outcome> {
44        // If the file doesn't exist, assume it's because
45        // another atom is going to provide it.
46        if !self.path.exists() {
47            return Ok(Outcome {
48                side_effects: vec![],
49                should_run: true,
50            });
51        }
52
53        let metadata = match std::fs::metadata(&self.path) {
54            Ok(m) => m,
55            Err(err) => {
56                error!(
57                    "Couldn't get metadata for {}, rejecting atom: {}",
58                    &self.path.display(),
59                    err.to_string()
60                );
61
62                return Ok(Outcome {
63                    side_effects: vec![],
64                    should_run: false,
65                });
66            }
67        };
68
69        if let (Some(current_owner), Some(current_group)) = (
70            uzers::get_user_by_uid(metadata.uid()),
71            uzers::get_group_by_gid(metadata.gid()),
72        ) {
73            let requested_owner = match uzers::get_user_by_name(self.owner.as_str()) {
74                Some(owner) => owner,
75                None => {
76                    error!(
77                        "Skipping chown as requested owner, {}, does not exist",
78                        self.owner,
79                    );
80                    return Ok(Outcome {
81                        side_effects: vec![],
82                        should_run: false,
83                    });
84                }
85            };
86
87            let requested_group = match uzers::get_group_by_name(self.group.as_str()) {
88                Some(group) => group,
89                None => {
90                    error!(
91                        "Skipping chown as requested group, {}, does not exist",
92                        self.group,
93                    );
94
95                    return Ok(Outcome {
96                        side_effects: vec![],
97                        should_run: false,
98                    });
99                }
100            };
101
102            if current_owner.uid() != requested_owner.uid() {
103                return Ok(Outcome {
104                    side_effects: vec![],
105                    should_run: true,
106                });
107            }
108
109            if current_group.gid() != requested_group.gid() {
110                return Ok(Outcome {
111                    side_effects: vec![],
112                    should_run: true,
113                });
114            }
115        }
116
117        error!("Something happened here");
118
119        Ok(Outcome {
120            side_effects: vec![],
121            should_run: false,
122        })
123    }
124
125    fn execute(&mut self) -> anyhow::Result<()> {
126        if !self.owner.is_empty() {
127            self.path.set_owner(self.owner.as_str())?;
128        }
129
130        if !self.group.is_empty() {
131            self.path.set_group(self.group.as_str())?;
132        }
133
134        Ok(())
135    }
136}
137
138#[cfg(not(unix))]
139impl Atom for Chown {
140    fn plan(&self) -> anyhow::Result<Outcome> {
141        // Never run
142        Ok(Outcome {
143            side_effects: vec![],
144            should_run: false,
145        })
146    }
147
148    fn execute(&mut self) -> anyhow::Result<()> {
149        Ok(())
150    }
151}
152
153#[cfg(test)]
154#[cfg(unix)]
155mod tests {
156    use super::*;
157    use pretty_assertions::assert_eq;
158
159    #[test]
160    fn it_can() {
161        // Using unwrap_or_else which catches the CI build where the users
162        // crate can't seem to detect the user within a container.
163        // Which I know to be root.
164        let user = uzers::get_current_username()
165            .unwrap_or_else(|| std::ffi::OsString::from("root"))
166            .into_string()
167            .unwrap();
168
169        let group = uzers::get_current_groupname()
170            .unwrap_or_else(|| std::ffi::OsString::from("root"))
171            .into_string()
172            .unwrap();
173
174        let temp_file = match tempfile::NamedTempFile::new() {
175            std::result::Result::Ok(file) => file,
176            std::result::Result::Err(_) => {
177                assert_eq!(false, true);
178                return;
179            }
180        };
181
182        let file_chown = Chown {
183            path: temp_file.path().to_path_buf(),
184            owner: user.clone(),
185            group: group.clone(),
186        };
187
188        assert_eq!(false, file_chown.plan().unwrap().should_run);
189
190        let file_chown = Chown {
191            path: temp_file.path().to_path_buf(),
192            owner: user,
193            group: String::from("daemon"),
194        };
195
196        assert_eq!(true, file_chown.plan().unwrap().should_run);
197
198        let file_chown = Chown {
199            path: temp_file.path().to_path_buf(),
200            owner: String::from("root"),
201            group,
202        };
203
204        assert_eq!(true, file_chown.plan().unwrap().should_run);
205
206        let file_chown = Chown {
207            path: temp_file.path().to_path_buf(),
208            owner: String::from("root"),
209            group: String::from("daemon"),
210        };
211
212        assert_eq!(true, file_chown.plan().unwrap().should_run);
213    }
214}