libside/builder/
apt.rs

1use super::Context;
2pub use crate::generic_apt_package;
3use crate::graph::GraphNodeReference;
4use crate::requirements::{Requirement, Supports};
5use crate::system::{NeverError, System};
6use serde::{Deserialize, Serialize};
7use std::fmt::Display;
8
9#[macro_export]
10macro_rules! generic_apt_package {
11    ($vis:vis $struct:ident => $apt_package:literal) => {
12        $vis struct $struct($crate::graph::GraphNodeReference);
13
14        impl $crate::builder::apt::AptPackage for $struct {
15            const NAME: &'static str = $apt_package;
16
17            fn create(node: $crate::graph::GraphNodeReference) -> Self {
18                Self(node)
19            }
20
21            fn graph_node(&self) -> GraphNodeReference {
22                self.0
23            }
24        }
25    }
26}
27
28#[derive(Default)]
29pub struct Apt {
30    update: Option<GraphNodeReference>,
31    global_preconditions: Vec<GraphNodeReference>,
32}
33
34impl Apt {
35    pub fn global_precondition<R: Requirement>(context: &mut Context<R>, node: GraphNodeReference) {
36        let state = context.state::<Apt>();
37        state.global_preconditions.push(node);
38    }
39}
40
41pub trait AptPackage {
42    const NAME: &'static str;
43
44    fn create(node: GraphNodeReference) -> Self;
45
46    fn graph_node(&self) -> GraphNodeReference;
47
48    fn install<R: Requirement + Supports<AptInstall> + Supports<AptUpdate>>(
49        context: &mut Context<R>,
50    ) -> Self
51    where
52        Self: Sized,
53    {
54        if context.state::<Apt>().update.is_none() {
55            context.state::<Apt>().update = Some(context.add_node(AptUpdate, &[]));
56        }
57        let state = context.state::<Apt>();
58        let updated = state.update;
59        let dependencies = updated
60            .iter()
61            .chain(state.global_preconditions.iter())
62            .copied()
63            .collect::<Vec<_>>();
64        Self::create(context.add_node(AptInstall::new(Self::NAME), dependencies.iter()))
65    }
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
69pub struct AptInstall {
70    name: String,
71}
72
73impl AptInstall {
74    pub fn new(name: &str) -> AptInstall {
75        AptInstall {
76            name: name.to_string(),
77        }
78    }
79}
80
81#[derive(Debug, thiserror::Error)]
82pub enum InstallError<S: System> {
83    #[error("unable to execute apt-get: {0}")]
84    FailedToStart(S::CommandError),
85
86    #[error("apt-get failed: {0} {1}")]
87    Unsuccessful(String, String),
88}
89
90impl<S: System> From<(&str, &str)> for InstallError<S> {
91    fn from(output: (&str, &str)) -> Self {
92        InstallError::Unsuccessful(output.0.to_string(), output.1.to_string())
93    }
94}
95
96#[derive(Debug, thiserror::Error)]
97#[error("unable to execute apt-get: {0}")]
98pub struct CheckError<S: System>(S::CommandError);
99
100impl Requirement for AptInstall {
101    type CreateError<S: System> = InstallError<S>;
102    type ModifyError<S: System> = NeverError;
103    type DeleteError<S: System> = InstallError<S>;
104    type HasBeenCreatedError<S: System> = CheckError<S>;
105
106    fn create<S: crate::system::System>(&self, system: &mut S) -> Result<(), Self::CreateError<S>> {
107        let result = system
108            .execute_command(
109                "apt-get",
110                &[
111                    "install",
112                    "-y",
113                    "-q",
114                    "--no-install-recommends",
115                    self.name.as_str(),
116                ],
117            )
118            .map_err(InstallError::FailedToStart)?;
119        result.successful()?;
120
121        Ok(())
122    }
123
124    fn modify<S: crate::system::System>(
125        &self,
126        _system: &mut S,
127    ) -> Result<(), Self::ModifyError<S>> {
128        Ok(())
129    }
130
131    fn delete<S: crate::system::System>(&self, system: &mut S) -> Result<(), Self::DeleteError<S>> {
132        let result = system
133            .execute_command("apt-get", &["remove", "-y", "-q", &self.name])
134            .map_err(InstallError::FailedToStart)?;
135        result.successful()?;
136
137        Ok(())
138    }
139
140    fn has_been_created<S: crate::system::System>(
141        &self,
142        system: &mut S,
143    ) -> Result<bool, Self::HasBeenCreatedError<S>> {
144        let result = system
145            .execute_command("dpkg-query", &["-W", "-f=${Status}", &self.name])
146            .map_err(CheckError)?;
147        if result.is_success() {
148            Ok(result.stdout_as_str().starts_with("install"))
149        } else {
150            Ok(false)
151        }
152    }
153
154    fn affects(&self, other: &Self) -> bool {
155        self.name == other.name
156    }
157
158    fn supports_modifications(&self) -> bool {
159        false
160    }
161    fn can_undo(&self) -> bool {
162        true
163    }
164    fn may_pre_exist(&self) -> bool {
165        true
166    }
167
168    fn verify<S: System>(&self, system: &mut S) -> Result<bool, ()> {
169        Ok(self.has_been_created(system).unwrap())
170    }
171
172    const NAME: &'static str = "apt_package";
173}
174
175impl Display for AptInstall {
176    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177        write!(f, "apt({})", self.name)
178    }
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
182pub struct AptUpdate;
183
184#[derive(Debug, thiserror::Error)]
185#[error("unable to execute apt-get: {0}")]
186pub struct UpdateError<S: System>(S::CommandError);
187
188impl Requirement for AptUpdate {
189    const NAME: &'static str = "apt_update";
190
191    type CreateError<S: System> = UpdateError<S>;
192    type ModifyError<S: System> = UpdateError<S>;
193    type DeleteError<S: System> = NeverError;
194    type HasBeenCreatedError<S: System> = NeverError;
195
196    fn create<S: System>(&self, system: &mut S) -> Result<(), Self::CreateError<S>> {
197        system
198            .execute_command("apt-get", &["update"])
199            .map_err(UpdateError)
200            .map(|_| ())
201    }
202
203    fn modify<S: System>(&self, system: &mut S) -> Result<(), Self::ModifyError<S>> {
204        self.create(system)
205    }
206
207    fn delete<S: System>(&self, _system: &mut S) -> Result<(), Self::DeleteError<S>> {
208        Ok(())
209    }
210
211    fn has_been_created<S: System>(
212        &self,
213        _system: &mut S,
214    ) -> Result<bool, Self::HasBeenCreatedError<S>> {
215        Ok(true)
216    }
217
218    fn affects(&self, _other: &Self) -> bool {
219        true
220    }
221
222    fn supports_modifications(&self) -> bool {
223        true
224    }
225
226    fn can_undo(&self) -> bool {
227        false
228    }
229
230    fn may_pre_exist(&self) -> bool {
231        true
232    }
233
234    fn verify<S: System>(&self, _system: &mut S) -> Result<bool, ()> {
235        Ok(true)
236    }
237}
238
239impl Display for AptUpdate {
240    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241        write!(f, "apt-update")
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use crate::{
248        builder::apt::{AptInstall, AptUpdate},
249        requirements::Requirement,
250        testing::LxcInstance,
251    };
252
253    #[test]
254    pub fn serialize_deserialize_apt_install() {
255        let r = AptInstall {
256            name: "test".to_string(),
257        };
258        let json = r#"{"name":"test"}"#;
259
260        assert_eq!(serde_json::to_string(&r).unwrap(), json);
261        assert_eq!(r, serde_json::from_str(json).unwrap());
262    }
263
264    #[test]
265    pub fn serialize_deserialize_apt_update() {
266        let r = AptUpdate;
267        let json = r#"null"#;
268
269        assert_eq!(serde_json::to_string(&r).unwrap(), json);
270        assert_eq!(r, serde_json::from_str(json).unwrap());
271    }
272
273    #[test]
274    #[ignore]
275    pub fn lxc_apt_install() {
276        let mut sys = LxcInstance::start(LxcInstance::DEFAULT_IMAGE);
277        let p = AptInstall {
278            name: "nginx".to_string(),
279        };
280
281        assert!(!p.has_been_created(&mut sys).unwrap());
282        assert!(!p.verify(&mut sys).unwrap());
283
284        p.create(&mut sys).unwrap();
285
286        assert!(p.has_been_created(&mut sys).unwrap());
287        assert!(p.verify(&mut sys).unwrap());
288
289        p.delete(&mut sys).unwrap();
290
291        assert!(!p.has_been_created(&mut sys).unwrap());
292        assert!(!p.verify(&mut sys).unwrap());
293    }
294
295    #[test]
296    #[ignore]
297    pub fn lxc_apt_update() {
298        let mut sys = LxcInstance::start(LxcInstance::DEFAULT_IMAGE);
299        let p = AptUpdate;
300
301        p.create(&mut sys).unwrap();
302        p.modify(&mut sys).unwrap();
303        p.delete(&mut sys).unwrap();
304    }
305}