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}