Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add copy target type #79

Closed
wants to merge 12 commits into from
202 changes: 198 additions & 4 deletions src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,25 @@ use anyhow::{Context, Result};
use crossterm::style::Colorize;
use handlebars::Handlebars;

use crate::config::{SymbolicTarget, TemplateTarget, Variables};
use crate::config::{CopyTarget, SymbolicTarget, TemplateTarget, Variables};
use crate::difference;
use crate::filesystem::{Filesystem, SymlinkComparison, TemplateComparison};
use crate::filesystem::{CopyComparison, Filesystem, SymlinkComparison, TemplateComparison};

#[cfg_attr(test, mockall::automock)]
pub trait ActionRunner {
fn delete_symlink(&mut self, source: &Path, target: &Path) -> Result<bool>;
fn delete_copy(&mut self, source: &Path, target: &Path) -> Result<bool>;
fn delete_template(&mut self, source: &Path, cache: &Path, target: &Path) -> Result<bool>;
fn create_symlink(&mut self, source: &Path, target: &SymbolicTarget) -> Result<bool>;
fn create_copy(&mut self, source: &Path, target: &CopyTarget) -> Result<bool>;
fn create_template(
&mut self,
source: &Path,
cache: &Path,
target: &TemplateTarget,
) -> Result<bool>;
fn update_symlink(&mut self, source: &Path, target: &SymbolicTarget) -> Result<bool>;
fn update_copy(&mut self, source: &Path, target: &CopyTarget) -> Result<bool>;
fn update_template(
&mut self,
source: &Path,
Expand Down Expand Up @@ -58,12 +61,18 @@ impl<'a> ActionRunner for RealActionRunner<'a> {
fn delete_symlink(&mut self, source: &Path, target: &Path) -> Result<bool> {
delete_symlink(source, target, self.fs, self.force)
}
fn delete_copy(&mut self, source: &Path, target: &Path) -> Result<bool> {
delete_copy(source, target, self.fs, self.force)
}
fn delete_template(&mut self, source: &Path, cache: &Path, target: &Path) -> Result<bool> {
delete_template(source, cache, target, self.fs, self.force)
}
fn create_symlink(&mut self, source: &Path, target: &SymbolicTarget) -> Result<bool> {
create_symlink(source, target, self.fs, self.force)
}
fn create_copy(&mut self, source: &Path, target: &CopyTarget) -> Result<bool> {
create_copy(source, target, self.fs, self.force)
}
fn create_template(
&mut self,
source: &Path,
Expand All @@ -83,6 +92,9 @@ impl<'a> ActionRunner for RealActionRunner<'a> {
fn update_symlink(&mut self, source: &Path, target: &SymbolicTarget) -> Result<bool> {
update_symlink(source, target, self.fs, self.force)
}
fn update_copy(&mut self, source: &Path, target: &CopyTarget) -> Result<bool> {
update_copy(source, target, self.fs, self.force)
}
fn update_template(
&mut self,
source: &Path,
Expand Down Expand Up @@ -158,6 +170,58 @@ fn perform_symlink_target_deletion(fs: &mut dyn Filesystem, target: &Path) -> Re
Ok(())
}

/// Returns true if copy should be deleted from cache
pub fn delete_copy(
source: &Path,
target: &Path,
fs: &mut dyn Filesystem,
force: bool,
) -> Result<bool> {
info!("{} copy {:?} -> {:?}", "[-]".red(), source, target);

let comparison = fs
.compare_copy(source, target)
.context("detect copy's current state")?;
debug!("Current state: {}", comparison);

match comparison {
CopyComparison::Identical | CopyComparison::OnlyTargetExists => {
debug!("Performing deletion");
perform_copy_target_deletion(fs, target).context("perform copy target deletion")?;
Ok(true)
}
CopyComparison::OnlySourceExists | CopyComparison::BothMissing => {
warn!(
"Deleting copy {:?} -> {:?} but target doesn't exist. Removing from cache anyways.",
source, target
);
Ok(true)
}
CopyComparison::Changed | CopyComparison::TargetNotRegularFile if force => {
warn!(
"Deleting copy {:?} -> {:?} but {}. Forcing.",
source, target, comparison
);
perform_copy_target_deletion(fs, target).context("perform copy target deletion")?;
Ok(true)
}
CopyComparison::Changed | CopyComparison::TargetNotRegularFile => {
error!(
"Deleting {:?} -> {:?} but {}. Skipping.",
source, target, comparison
);
Ok(false)
}
}
}

fn perform_copy_target_deletion(fs: &mut dyn Filesystem, target: &Path) -> Result<()> {
fs.remove_file(target).context("remove copy")?;
fs.delete_parents(target, false)
.context("delete parents of copy")?;
Ok(())
}

/// Returns true if template should be deleted from cache
pub fn delete_template(
source: &Path,
Expand Down Expand Up @@ -299,6 +363,67 @@ pub fn create_symlink(
}
}

/// Returns true if copy should be added to cache
pub fn create_copy(
source: &Path,
target: &CopyTarget,
fs: &mut dyn Filesystem,
force: bool,
) -> Result<bool> {
info!("{} copy {:?} -> {:?}", "[+]".green(), source, target.target);

let comparison = fs
.compare_copy(source, &target.target)
.context("detect copy's current state")?;
debug!("Current state: {}", comparison);

match comparison {
CopyComparison::OnlySourceExists => {
debug!("Performing creation");
fs.create_dir_all(
target
.target
.parent()
.context("get parent of target file")?,
&target.owner,
)
.context("create parent for target file")?;
fs.copy_file(source, &target.target, &target.owner)
.context("create target copy")?;
Ok(true)
}
CopyComparison::Identical => {
warn!("Creating copy {:?} -> {:?} but target already exists and points at source. Adding to cache anyways", source, target.target);
Ok(true)
}
CopyComparison::OnlyTargetExists | CopyComparison::BothMissing => {
error!(
"Creating copy {:?} -> {:?} but {}. Skipping.",
source, target.target, comparison
);
Ok(false)
}
CopyComparison::Changed | CopyComparison::TargetNotRegularFile if force => {
warn!(
"Creating copy {:?} -> {:?} but {}. Forcing.",
source, target.target, comparison
);
fs.remove_file(&target.target)
.context("remove copy target while forcing")?;
fs.copy_file(source, &target.target, &target.owner)
.context("create target copy")?;
Ok(true)
}
CopyComparison::Changed | CopyComparison::TargetNotRegularFile => {
error!(
"Creating copy {:?} -> {:?} but {}. Skipping.",
source, target.target, comparison
);
Ok(false)
}
}
}

/// Returns true if the template should be added to cache
pub fn create_template(
source: &Path,
Expand Down Expand Up @@ -456,6 +581,72 @@ pub fn update_symlink(
}
}

/// Returns true if the symlink wasn't skipped
pub fn update_copy(
source: &Path,
target: &CopyTarget,
fs: &mut dyn Filesystem,
force: bool,
) -> Result<bool> {
debug!("Updating copy {:?} -> {:?}...", source, target.target);

let comparison = fs
.compare_copy(&source, &target.target)
.context("detect copy's current state")?;
debug!("Current state: {}", comparison);

match comparison {
CopyComparison::Identical => {
debug!("Performing update");
fs.set_owner(&target.target, &target.owner)
.context("set target copy owner")?;
Ok(true)
}
CopyComparison::OnlyTargetExists | CopyComparison::BothMissing => {
error!(
"Updating copy {:?} -> {:?} but source is missing. Skipping.",
source, target.target
);
Ok(false)
}
CopyComparison::Changed | CopyComparison::TargetNotRegularFile if force => {
warn!(
"Updating copy {:?} -> {:?} but {}. Forcing.",
source, target.target, comparison
);
fs.remove_file(&target.target)
.context("remove copy target while forcing")?;
fs.copy_file(source, &target.target, &target.owner)
.context("create target copy")?;
Ok(true)
}
CopyComparison::Changed | CopyComparison::TargetNotRegularFile => {
error!(
"Updating copy {:?} -> {:?} but {}. Skipping.",
source, target.target, comparison
);
Ok(false)
}
CopyComparison::OnlySourceExists => {
warn!(
"Updating copy {:?} -> {:?} but {}. Creating it anyways.",
source, target.target, comparison
);
fs.create_dir_all(
target
.target
.parent()
.context("get parent of target file")?,
&target.owner,
)
.context("create parent for target file")?;
fs.copy_file(source, &target.target, &target.owner)
.context("create target copy")?;
Ok(true)
}
}
}

/// Returns true if the template was not skipped
#[allow(clippy::too_many_arguments)]
pub fn update_template(
Expand Down Expand Up @@ -551,13 +742,16 @@ pub(crate) fn perform_template_deploy(
handlebars: &Handlebars<'_>,
variables: &Variables,
) -> Result<()> {
let file_contents = fs
let original_file_contents = fs
.read_to_string(&source)
.context("read template source file")?;
let file_contents = target.apply_actions(file_contents);
let file_contents = target.apply_actions(original_file_contents.clone());
let rendered = handlebars
.render_template(&file_contents, variables)
.context("render template")?;
if original_file_contents == rendered {
warn!("File {:?} is specified as 'template' but is not a templated file. Consider using 'copy' instead.", source);
}

// Cache
fs.create_dir_all(&cache.parent().context("get parent of cache file")?, &None)
Expand Down
Loading