rustic_rs/config/
hooks.rs

1//! rustic hooks configuration
2//!
3//! Hooks are commands that are executed before and after every rustic operation.
4//! They can be used to run custom scripts or commands before and after a backup,
5//! copy, forget, prune or other operation.
6//!
7//! Depending on the hook type, the command is being executed at a different point
8//! in the lifecycle of the program. The following hooks are available:
9//!
10//! - global hooks
11//! - repository hooks
12//! - backup hooks
13//! - specific source-related hooks
14
15use std::collections::HashMap;
16
17use anyhow::Result;
18use conflate::Merge;
19use serde::{Deserialize, Serialize};
20
21use rustic_core::CommandInput;
22
23#[derive(Debug, Default, Clone, Serialize, Deserialize, Merge)]
24#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
25pub struct Hooks {
26    /// Call this command before every rustic operation
27    #[merge(strategy = conflate::vec::append)]
28    pub run_before: Vec<CommandInput>,
29
30    /// Call this command after every successful rustic operation
31    #[merge(strategy = conflate::vec::append)]
32    pub run_after: Vec<CommandInput>,
33
34    /// Call this command after every failed rustic operation
35    #[merge(strategy = conflate::vec::append)]
36    pub run_failed: Vec<CommandInput>,
37
38    /// Call this command after every rustic operation
39    #[merge(strategy = conflate::vec::append)]
40    pub run_finally: Vec<CommandInput>,
41
42    #[serde(skip)]
43    #[merge(skip)]
44    pub context: String,
45
46    #[serde(skip)]
47    #[merge(skip)]
48    pub env: HashMap<String, String>,
49}
50
51impl Hooks {
52    pub fn with_context(&self, context: &str) -> Self {
53        let mut hooks = self.clone();
54        hooks.context = context.to_string();
55        hooks
56    }
57
58    pub fn with_env(&self, env: &HashMap<String, String>) -> Self {
59        let mut hooks = self.clone();
60        hooks.env = HashMap::<String, String>::new();
61        for (key, val) in env.iter() {
62            _ = hooks.env.insert(key.clone(), val.clone());
63        }
64        hooks
65    }
66
67    fn run_all(
68        cmds: &[CommandInput],
69        context: &str,
70        what: &str,
71        env: &HashMap<String, String>,
72    ) -> Result<()> {
73        let mut env = env.clone();
74
75        let _ = env.insert("RUSTIC_HOOK_TYPE".to_string(), what.to_string());
76
77        for cmd in cmds {
78            cmd.run(context, what, &env)?;
79        }
80
81        Ok(())
82    }
83
84    pub fn run_before(&self) -> Result<()> {
85        Self::run_all(&self.run_before, &self.context, "run-before", &self.env)
86    }
87
88    pub fn run_after(&self) -> Result<()> {
89        Self::run_all(&self.run_after, &self.context, "run-after", &self.env)
90    }
91
92    pub fn run_failed(&self) -> Result<()> {
93        Self::run_all(&self.run_failed, &self.context, "run-failed", &self.env)
94    }
95
96    pub fn run_finally(&self) -> Result<()> {
97        Self::run_all(&self.run_finally, &self.context, "run-finally", &self.env)
98    }
99
100    /// Run the given closure using the specified hooks.
101    ///
102    /// Note: after a failure no error handling is done for the hooks `run_failed`
103    /// and `run_finally` which must run after. However, they already log a warning
104    /// or error depending on the `on_failure` setting.
105    pub fn use_with<T>(&self, f: impl FnOnce() -> Result<T>) -> Result<T> {
106        match self.run_before() {
107            Ok(()) => match f() {
108                Ok(result) => match self.run_after() {
109                    Ok(()) => {
110                        self.run_finally()?;
111                        Ok(result)
112                    }
113                    Err(err_after) => {
114                        _ = self.run_finally();
115                        Err(err_after)
116                    }
117                },
118                Err(err_f) => {
119                    _ = self.run_failed();
120                    _ = self.run_finally();
121                    Err(err_f)
122                }
123            },
124            Err(err_before) => {
125                _ = self.run_failed();
126                _ = self.run_finally();
127                Err(err_before)
128            }
129        }
130    }
131}