-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgitops.rs
More file actions
159 lines (128 loc) · 5.3 KB
/
gitops.rs
File metadata and controls
159 lines (128 loc) · 5.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
use crate::{GitHubAppConfig, GitHubError, GitHubTokenProvider};
use serde::de::DeserializeOwned;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use tracing::{debug, error, info, instrument, warn};
pub struct GitHubGitOps {
config: GitHubAppConfig,
token_provider: GitHubTokenProvider,
}
impl GitHubGitOps {
pub fn new(config: GitHubAppConfig, token_provider: GitHubTokenProvider) -> Self {
Self {
config,
token_provider,
}
}
#[instrument(skip(self, args), fields(git_cmd = ?args))]
fn run_git_command(&self, args: &[&str], cwd: Option<&Path>) -> Result<String, GitHubError> {
debug!("Executing git command");
let mut cmd = Command::new("git");
cmd.args(args).stdout(Stdio::piped()).stderr(Stdio::piped());
if let Some(dir) = cwd {
cmd.current_dir(dir);
debug!(directory = ?dir, "Set working directory");
}
let output = cmd.output()?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
// Log the error with tracing - the sanitizer will redact tokens automatically
error!(
exit_code = ?output.status.code(),
stderr = %stderr,
"Git command failed"
);
// Return a generic error to users (details are in logs)
return Err(GitHubError::Git(format!(
"Git command failed. Check logs for details. Exit code: {:?}",
output.status.code()
)));
}
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
debug!(output_length = stdout.len(), "Git command succeeded");
Ok(stdout)
}
#[instrument(skip(self), fields(repo = %self.config.repo, branch = %self.config.branch))]
pub async fn initialize(&self) -> Result<(), GitHubError> {
if self.config.git_clone_path.exists() {
info!("Repository already exists, skipping clone");
return Ok(());
}
info!("Initializing repository clone");
std::fs::create_dir_all(&self.config.git_clone_path)?;
debug!("Created clone directory");
let token = self.token_provider.get_token().await?;
let clone_url = format!(
"https://x-access-token:{}@github.com/{}.git",
token, self.config.repo
);
// Tracing will automatically sanitize the clone_url in logs
debug!("Starting git clone operation");
self.run_git_command(
&["clone", "--branch", &self.config.branch, &clone_url, "."],
Some(&self.config.git_clone_path),
)?;
info!("Repository clone completed successfully");
Ok(())
}
#[instrument(skip(self), fields(repo = %self.config.repo, branch = %self.config.branch))]
pub async fn sync(&self) -> Result<(), GitHubError> {
if !self.config.git_clone_path.exists() {
warn!("Repository not initialized");
return Err(GitHubError::Git(
"Repository not initialized. Call initialize() first.".to_string(),
));
}
info!("Syncing repository with remote");
let token = self.token_provider.get_token().await?;
let remote_url = format!(
"https://x-access-token:{}@github.com/{}.git",
token, self.config.repo
);
// Tracing will automatically sanitize the remote_url in logs
debug!("Updating remote URL");
self.run_git_command(
&["remote", "set-url", "origin", &remote_url],
Some(&self.config.git_clone_path),
)?;
debug!("Fetching from origin");
self.run_git_command(&["fetch", "origin"], Some(&self.config.git_clone_path))?;
let remote_branch = format!("origin/{}", self.config.branch);
debug!(remote_branch = %remote_branch, "Resetting to remote branch");
self.run_git_command(
&["reset", "--hard", &remote_branch],
Some(&self.config.git_clone_path),
)?;
info!("Repository sync completed successfully");
Ok(())
}
#[instrument(skip(self), fields(repo = %self.config.repo, glob = %self.config.manifest_glob))]
pub fn load_all_manifests<T: DeserializeOwned>(&self) -> Result<Vec<T>, GitHubError> {
if !self.config.git_clone_path.exists() {
warn!("Repository not initialized");
return Err(GitHubError::Git(
"Repository not initialized. Call initialize() first.".to_string(),
));
}
debug!("Loading manifests");
let pattern = self
.config
.git_clone_path
.join(&self.config.manifest_glob)
.to_string_lossy()
.to_string();
let mut manifests = Vec::new();
for entry in glob::glob(&pattern)? {
let path = entry?;
debug!(file = ?path, "Loading manifest file");
let content = std::fs::read_to_string(&path)?;
let manifest: T = serde_yaml::from_str(&content).map_err(GitHubError::Yaml)?;
manifests.push(manifest);
}
info!(count = manifests.len(), "Loaded manifests");
Ok(manifests)
}
pub fn repo_path(&self) -> &PathBuf {
&self.config.git_clone_path
}
}