eol_deployer/
deployment_manager.rs1use crate::{config::Config, docker_client::DockerClient, git_client::GitClient};
2
3pub struct DeploymentManager {
4 docker: DockerClient,
5 git: GitClient,
6}
7
8impl DeploymentManager {
9 pub fn new(socket_path: String) -> Self {
10 Self {
11 docker: DockerClient::new(socket_path),
12 git: GitClient,
13 }
14 }
15
16 pub async fn rolling_deploy(
17 &self,
18 project_name: &str,
19 tag: &str,
20 config: &Config,
21 ) -> Result<(), Box<dyn std::error::Error>> {
22 println!(
23 "Starting rolling deployment for project '{}' with tag '{}'",
24 project_name, tag
25 );
26
27 let new_config_path = self
29 .git
30 .clone_repository_to_versioned_path(&config.repo_url, tag, &config.mount_path)
31 .await?;
32
33 let running_containers = self
35 .docker
36 .get_running_traefik_containers(project_name)
37 .await?;
38
39 if running_containers.is_empty() {
40 return Err(format!(
41 "No running Traefik containers found for project '{}'",
42 project_name
43 )
44 .into());
45 }
46
47 println!(
48 "Found {} running Traefik containers",
49 running_containers.len()
50 );
51
52 for (index, container) in running_containers.iter().enumerate() {
54 let new_container_name = format!(
55 "{}-{}-{}",
56 container.names[0].trim_start_matches('/'),
57 tag,
58 index
59 );
60
61 println!("Rolling {} -> {}", container.names[0], new_container_name);
62
63 let new_container_id = self
65 .docker
66 .create_container_from_existing(
67 container,
68 &new_container_name,
69 &new_config_path, )
71 .await?;
72
73 self.docker.start_container(&new_container_id).await?;
75
76 tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
78
79 println!("New container {} is starting up...", new_container_name);
81
82 println!("Stopping old container {}", container.names[0]);
84 self.docker.stop_container(&container.id).await?;
85 self.docker.remove_container(&container.id).await?;
86
87 println!("Successfully rolled {} to new version", container.names[0]);
88 }
89
90 self.cleanup_old_configs(&config.mount_path, 3).await?;
92
93 println!("Rolling deployment completed successfully!");
94 Ok(())
95 }
96
97 async fn cleanup_old_configs(
98 &self,
99 base_path: &str,
100 keep_versions: usize,
101 ) -> Result<(), Box<dyn std::error::Error>> {
102 let mut config_dirs = Vec::new();
103
104 if let Ok(entries) = std::fs::read_dir(base_path) {
105 for entry in entries {
106 if let Ok(entry) = entry {
107 let path = entry.path();
108 if path.is_dir() {
109 if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
110 if name.starts_with("traefik-config-") {
111 config_dirs.push(path);
112 }
113 }
114 }
115 }
116 }
117 }
118
119 config_dirs.sort_by_key(|path| {
121 std::fs::metadata(path)
122 .and_then(|m| m.created())
123 .unwrap_or(std::time::SystemTime::UNIX_EPOCH)
124 });
125 config_dirs.reverse();
126
127 for old_config in config_dirs.iter().skip(keep_versions) {
129 println!("Cleaning up old config: {:?}", old_config);
130 if let Err(e) = std::fs::remove_dir_all(old_config) {
131 eprintln!("Failed to remove old config {:?}: {}", old_config, e);
132 }
133 }
134
135 Ok(())
136 }
137
138 pub async fn rollback(
139 &self,
140 project_name: &str,
141 tag: &str,
142 config: &Config,
143 ) -> Result<(), Box<dyn std::error::Error>> {
144 println!(
145 "Starting rollback of project '{}' to tag '{}'",
146 project_name, tag
147 );
148
149 let target_config_path = format!("{}/traefik-config-{}", config.mount_path, tag);
151
152 if !std::path::Path::new(&target_config_path).exists() {
153 println!("Target config not found locally, cloning...");
155 self.git
156 .clone_repository_to_versioned_path(&config.repo_url, tag, &config.mount_path)
157 .await?;
158 } else {
159 println!("Using existing config at {}", target_config_path);
160 }
161
162 self.rolling_deploy(project_name, tag, config).await?;
164
165 println!("Rollback completed successfully!");
166 Ok(())
167 }
168}