trappo/steps/
core.rs

1//! Contains the core steps included out of the box which are used during a deployment
2//! when the recipe builder method `.with_core_steps` is invoked.
3//!
4//! When a deployment is run, there are certain steps that are necessary for every deployment.
5//! These include things like initial set up of directories, setting up symlinks and cleaning up old releases
6//! after a successfull deploy.
7//!
8//! The only exception is the `RawCmdStep`. This struct is what commands feed to the program using an external
9//! `.trappo/steps.toml` file end up being. And they can be executed during deployment by invoking the
10//! recipe builder's `.with_steps_from_file(file_name: String)` method. <- TODO implementation.
11
12use steps::{Step, Context, error::StepError};
13use config::steps::StepConfig;
14use cmd::*;
15use display::*;
16
17/// This struct is what commands feed to the program using an external
18/// `.trappo/steps.toml` file end up being. And they can be executed during deployment by invoking the
19/// recipe builder's `.with_steps_from_file(file_name: String)` method.
20///
21/// It executes the command provided in the config file directly on the server, if the command fails
22/// it returns a critial error which will trigger a rollback. The output of the command is shown in the terminal.
23pub struct RawCmdStep { name: String, raw_cmd: String }
24
25impl Step for RawCmdStep {
26
27    fn execute (&self, context: &Context) -> Result<(), StepError> {
28
29        let server_command = format!("cd {} && {}", context.release_path.trim(), self.raw_cmd);
30        let status = exec_remote_cmd_inherit_output(&context.config.host, &server_command)?;
31
32        if !status.success() {
33            return Err(StepError::from_failed_command(&self.raw_cmd, status.code()));
34        }
35
36        Ok(())
37    }
38
39    fn get_name(&self) -> &str {
40        &self.name
41    }
42}
43
44impl From<StepConfig> for RawCmdStep {
45    fn from(config_step: StepConfig) -> Self {
46        RawCmdStep { name: config_step.name, raw_cmd: config_step.comand}
47    }
48}
49
50/// It performs the initial setup on the server prior to deployment.
51/// Basically creating the deployment path folder if it doesn't already exist.
52pub struct InitStep;
53
54impl Step for InitStep {
55
56    fn execute (&self, context: &Context) -> Result<(), StepError> {
57        let create_release_path_cmd = format!("mkdir -p {}", context.release_path);
58
59        let status = exec_remote_cmd(&context.config.host, &create_release_path_cmd)?.status;
60
61        if !status.success() {
62            return Err(StepError::from_failed_command(&create_release_path_cmd, status.code()));
63        }
64
65        Ok(())
66    }
67
68    fn get_name(&self) -> &str {
69        "core:init"
70    }
71}
72
73pub struct LinkFiles;
74
75impl Step for LinkFiles {
76
77    fn execute (&self, context: &Context) -> Result<(), StepError> {
78
79        for file in context.config.link_files.iter() {
80            let shared_file_path = format!("{}/{}", context.shared_path, file);
81            let symlink_path     = format!("{}/{}", context.release_path.trim(), file);
82
83            let file_exists = exec_remote_file_exists(&context.config.host, &shared_file_path, FSResourceType::File)?;
84
85            if !file_exists {
86                let error_msg = format!("Could not create symlink for file {} because it doesn't exist", file);
87                return Err(StepError::Critical(error_msg));
88            }
89
90            let symlink_command = format!("ln -s {} {}",shared_file_path, symlink_path);
91
92            let status = exec_remote_cmd(&context.config.host, &symlink_command)?.status;
93
94            if !status.success() {
95                return Err(StepError::from_failed_command(&symlink_command, status.code()));
96            }
97        }
98
99        Ok(())
100    }
101
102    fn get_name(&self) -> &str {
103        "core:link:files"
104    }
105}
106
107pub struct LinkDirs;
108
109impl Step for LinkDirs {
110
111    fn execute (&self, context: &Context) -> Result<(), StepError> {
112
113        for dir in context.config.link_dirs.iter() {
114            let shared_dir_path = format!("{}/{}", context.shared_path, dir);
115            let symlink_path    = format!("{}/{}", context.release_path.trim(), dir);
116
117            let dir_exists = exec_remote_file_exists(&context.config.host, &shared_dir_path, FSResourceType::Directory)?;
118
119            if !dir_exists {
120                let error_msg = format!("Could not create symlink for dir {} because it doesn't exist", dir);
121                return Err(StepError::Critical(error_msg));
122            }
123
124            let symlink_command = format!("ln -s {} {}", shared_dir_path, symlink_path);
125            let status = exec_remote_cmd(&context.config.host, &symlink_command)?.status;
126
127            if !status.success() {
128                return Err(StepError::from_failed_command(&symlink_command, status.code()));
129            }
130        }
131
132        Ok(())
133    }
134
135    fn get_name(&self) -> &str {
136        "core:link:directories"
137    }
138}
139
140pub struct SymlinkCurrent;
141
142impl Step for SymlinkCurrent {
143
144    fn execute (&self, context: &Context) -> Result<(), StepError> {
145
146        let current_symlink_path = format!("{}/current", context.config.deploy_path);
147
148        let current_symlink_exist = exec_remote_file_exists(&context.config.host, &current_symlink_path, FSResourceType::Symlink)?;
149
150        if current_symlink_exist {
151            let remove_current_command = format!("rm {}", current_symlink_path);
152
153            let status = exec_remote_cmd(&context.config.host, &remove_current_command)?.status;
154
155            if !status.success() {
156                return Err(StepError::from_failed_command(&remove_current_command, status.code()));
157            }
158        }
159
160        let create_current_symlink_cmd = format!("ln -s {} {}", context.release_path.trim(), current_symlink_path);
161
162        let status = exec_remote_cmd(&context.config.host, &create_current_symlink_cmd)?.status;
163
164        if !status.success() {
165            return Err(StepError::from_failed_command(&create_current_symlink_cmd, status.code()));
166        }
167
168        Ok(())
169    }
170
171    fn get_name(&self) -> &str {
172        "core:link:current"
173    }
174}
175
176pub struct CleanUpReleases;
177
178impl Step for CleanUpReleases {
179
180    fn execute (&self, context: &Context) -> Result<(), StepError> {
181
182        let mut releases   = exec_remote_fetch_sorted_filenames_in_dir(&context.config.host, &context.releases_path, SortOrder::Asc)
183            .map_err(|e| StepError::non_critical_from_error(e))?;
184
185        let keep_releases  = context.config.keep_releases as usize;
186        let total_releases = releases.len();
187
188        if total_releases <= keep_releases { return Ok(()); };
189        let to_remove = total_releases - keep_releases;
190        releases.resize(to_remove, "".into());
191
192        for release_dir in &releases {
193            let delete_dir_cmd = format!("rm -rf {}/{}", &context.releases_path, release_dir);
194            let output = exec_remote_cmd(&context.config.host, &delete_dir_cmd)?;
195
196            if !output.status.success() {
197                render_error(&format!("Failed to clean up old release {}", release_dir));
198            }
199
200            println!("Deleted: {}", release_dir);
201        }
202
203        Ok(())
204    }
205
206
207    fn get_name(&self) -> &str {
208        "core:cleanup:releases"
209    }
210}