Skip to main content

ambient_ci/action_impl/
tar.rs

1use std::path::PathBuf;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    action::{ActionError, Context},
7    action_impl::ActionImpl,
8    vdrive::VirtualDriveBuilder,
9};
10
11/// Create a `tar` archive from a directory.
12///
13/// This is meant for internal use by Ambient. It can't be used in any kind
14/// of plan, pre-plan, or post-plan. It can be used in a runnable plan.
15/// It is generated by Ambient to set up execution of a runnable plan.
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub struct TarCreate {
18    archive: PathBuf,
19    directory: PathBuf,
20}
21
22impl TarCreate {
23    /// Create a new `TarCreate` action.
24    pub fn new(archive: PathBuf, directory: PathBuf) -> Self {
25        Self { archive, directory }
26    }
27}
28
29impl ActionImpl for TarCreate {
30    fn execute(&self, _context: &mut Context) -> Result<(), ActionError> {
31        VirtualDriveBuilder::default()
32            .filename(&self.archive)
33            .root_directory(&self.directory)
34            .create()
35            .map_err(|e| TarError::TarCreate(self.archive.clone(), self.directory.clone(), e))?;
36        Ok(())
37    }
38}
39
40/// Extract a tar archive into a directory.
41///
42/// This is meant for internal use by Ambient. It can't be used in any kind
43/// of plan, pre-plan, or post-plan. It can be used in a runnable plan.
44/// It is generated by Ambient to set up execution of a runnable plan.
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub struct TarExtract {
47    archive: PathBuf,
48    directory: PathBuf,
49}
50
51impl TarExtract {
52    /// Create a new `TarExtract` action.
53    pub fn new(archive: PathBuf, directory: PathBuf) -> Self {
54        Self { archive, directory }
55    }
56}
57
58impl ActionImpl for TarExtract {
59    fn execute(&self, _context: &mut Context) -> Result<(), ActionError> {
60        let tar = VirtualDriveBuilder::default()
61            .filename(&self.archive)
62            .root_directory(&self.directory)
63            .open()
64            .map_err(|e| TarError::TarOpen(self.archive.clone(), e))?;
65        tar.extract_to(&self.directory)
66            .map_err(|e| TarError::TarExtract(self.archive.clone(), self.directory.clone(), e))?;
67        Ok(())
68    }
69}
70
71/// Errors from tar actions.
72#[derive(Debug, thiserror::Error)]
73pub enum TarError {
74    /// Can't create a tar archive.
75    #[error("failed to create tar archive {0} from {1}")]
76    TarCreate(PathBuf, PathBuf, #[source] crate::vdrive::VirtualDriveError),
77
78    /// Can't open a tar archive to extract it.
79    #[error("failed to open tar archive {0}")]
80    TarOpen(PathBuf, #[source] crate::vdrive::VirtualDriveError),
81
82    /// Can't extract a tar archive.
83    #[error("failed to extract tar archive {0} into {1}")]
84    TarExtract(PathBuf, PathBuf, #[source] crate::vdrive::VirtualDriveError),
85}
86
87impl From<TarError> for ActionError {
88    fn from(value: TarError) -> Self {
89        Self::Tar(value)
90    }
91}
92
93#[cfg(test)]
94mod test {
95    use crate::{
96        action::{RunnableAction, UnsafeAction},
97        plan::RunnablePlan,
98        runlog::RunLog,
99    };
100
101    use super::*;
102    use tempfile::tempdir;
103
104    fn plan() -> RunnablePlan {
105        let mut plan = RunnablePlan::default();
106        plan.set_cache_dir("/tmp");
107        plan.set_deps_dir("/tmp");
108        plan.set_source_dir("/tmp");
109        plan.set_artifacts_dir("/tmp");
110        plan
111    }
112
113    #[test]
114    fn tar_create_action() -> Result<(), Box<dyn std::error::Error>> {
115        let tmp = tempdir()?;
116        let src = tmp.path().join("src");
117        let tar = tmp.path().join("src.tar");
118
119        std::fs::create_dir(&src)?;
120        let action = RunnableAction::from_unsafe_action(&UnsafeAction::tar_create(&tar, &src));
121        let plan = plan();
122        let mut runlog = RunLog::default();
123        let mut context = Context::new(&mut runlog);
124        context.set_envs_from_plan(&plan)?;
125
126        assert!(!tar.exists());
127        assert!(action.execute(&mut context).is_ok());
128        assert!(tar.exists());
129        Ok(())
130    }
131
132    #[test]
133    fn tar_extract_action() -> Result<(), Box<dyn std::error::Error>> {
134        let tmp = tempdir()?;
135        let src = tmp.path().join("src");
136        let tar = tmp.path().join("src.tar");
137        let extracted = tmp.path().join("extracted");
138
139        std::fs::create_dir(&src)?;
140        std::fs::File::create(src.join("file.dat"))?;
141        let plan = plan();
142        let mut runlog = RunLog::default();
143        let mut context = Context::new(&mut runlog);
144        context.set_envs_from_plan(&plan)?;
145
146        RunnableAction::from_unsafe_action(&UnsafeAction::tar_create(&tar, &src))
147            .execute(&mut context)?;
148
149        let action =
150            RunnableAction::from_unsafe_action(&UnsafeAction::tar_extract(&tar, &extracted));
151        assert!(action.execute(&mut context).is_ok());
152        assert!(extracted.join("file.dat").exists());
153        Ok(())
154    }
155}