/ src / action_impl / tar.rs
tar.rs
  1  use std::path::PathBuf;
  2  
  3  use serde::{Deserialize, Serialize};
  4  
  5  use 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)]
 17  pub struct TarCreate {
 18      archive: PathBuf,
 19      directory: PathBuf,
 20  }
 21  
 22  impl TarCreate {
 23      /// Create a new `TarCreate` action.
 24      pub fn new(archive: PathBuf, directory: PathBuf) -> Self {
 25          Self { archive, directory }
 26      }
 27  }
 28  
 29  impl 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)]
 46  pub struct TarExtract {
 47      archive: PathBuf,
 48      directory: PathBuf,
 49  }
 50  
 51  impl TarExtract {
 52      /// Create a new `TarExtract` action.
 53      pub fn new(archive: PathBuf, directory: PathBuf) -> Self {
 54          Self { archive, directory }
 55      }
 56  }
 57  
 58  impl 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)]
 73  pub 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  
 87  impl From<TarError> for ActionError {
 88      fn from(value: TarError) -> Self {
 89          Self::Tar(value)
 90      }
 91  }
 92  
 93  #[cfg(test)]
 94  mod 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  }