rolling_deployer/
git_client.rs

1use tracing::info;
2
3pub struct GitClient;
4
5impl GitClient {
6    pub async fn clone_repository_to_versioned_path(
7        &self,
8        repo_url: &str,
9        tag: &str,
10        base_path: &str,
11    ) -> Result<String, Box<dyn std::error::Error>> {
12        let versioned_path = format!("{}/traefik-config-{}", base_path, tag);
13        let symlink_path = format!("{}/current", base_path);
14
15        info!(
16            "Cloning repository {} at tag {} to {}",
17            repo_url, tag, versioned_path
18        );
19
20        // Create parent directory if it doesn't exist
21        if let Some(parent) = std::path::Path::new(&versioned_path).parent() {
22            std::fs::create_dir_all(parent)?;
23        }
24
25        // Only clone if the versioned directory does not exist
26        if !std::path::Path::new(&versioned_path).exists() {
27            // Clone the repository
28            let output = std::process::Command::new("git")
29                .args(&[
30                    "clone",
31                    "--depth",
32                    "1",
33                    "--branch",
34                    tag,
35                    repo_url,
36                    &versioned_path,
37                ])
38                .output()?;
39
40            if !output.status.success() {
41                return Err(format!(
42                    "Git clone failed: {}",
43                    String::from_utf8_lossy(&output.stderr)
44                )
45                .into());
46            }
47
48            info!(
49                "Successfully cloned {} at tag {} to {}",
50                repo_url, tag, versioned_path
51            );
52        } else {
53            info!("Using existing config at {}", versioned_path);
54        }
55
56        // Create or update the 'current' symlink
57        let symlink_path_obj = std::path::Path::new(&symlink_path);
58        if symlink_path_obj.exists() || symlink_path_obj.is_symlink() {
59            std::fs::remove_file(&symlink_path)?;
60        }
61        #[cfg(unix)]
62        std::os::unix::fs::symlink(&versioned_path, &symlink_path)?;
63        #[cfg(windows)]
64        std::os::windows::fs::symlink_dir(&versioned_path, &symlink_path)?;
65
66        Ok(symlink_path)
67    }
68
69    pub async fn fetch_latest(&self, repo_dir: &str) -> Result<(), Box<dyn std::error::Error>> {
70        info!("Fetching latest changes in {}", repo_dir);
71
72        let output = std::process::Command::new("git")
73            .args(&["fetch", "--all"])
74            .current_dir(repo_dir)
75            .output()?;
76
77        if !output.status.success() {
78            return Err(format!(
79                "Git fetch failed: {}",
80                String::from_utf8_lossy(&output.stderr)
81            )
82            .into());
83        }
84
85        Ok(())
86    }
87
88    pub async fn checkout_tag(
89        &self,
90        repo_dir: &str,
91        tag: &str,
92    ) -> Result<(), Box<dyn std::error::Error>> {
93        info!("Checking out tag {} in {}", tag, repo_dir);
94
95        let output = std::process::Command::new("git")
96            .args(&["checkout", tag])
97            .current_dir(repo_dir)
98            .output()?;
99
100        if !output.status.success() {
101            return Err(format!(
102                "Git checkout failed: {}",
103                String::from_utf8_lossy(&output.stderr)
104            )
105            .into());
106        }
107
108        info!("Successfully checked out tag {}", tag);
109        Ok(())
110    }
111}