#[derive(Debug, Clone)]
pub struct GitOpsConfig {
pub tool: String,
pub namespace: String,
pub auto_sync: bool,
pub prune: bool,
pub self_heal: bool,
pub timeout: u32,
}
#[derive(Debug, Clone)]
pub struct GitOpsSyncPolicy {
pub automatic: bool,
pub timeout: u32,
pub retry_limit: u32,
}
impl Default for GitOpsSyncPolicy {
fn default() -> Self {
Self {
automatic: true,
timeout: 300,
retry_limit: 5,
}
}
}
#[derive(Debug, Clone)]
pub struct ArgoCDApplication {
pub name: String,
pub environment: String,
pub repo_url: String,
pub target_revision: String,
pub path: String,
pub sync_policy: GitOpsSyncPolicy,
pub destination_namespace: String,
pub destination_server: String,
}
impl ArgoCDApplication {
pub fn new(
name: String,
environment: String,
repo_url: String,
target_revision: String,
path: String,
) -> Self {
Self {
name,
environment: environment.clone(),
repo_url,
target_revision,
path,
sync_policy: GitOpsSyncPolicy::default(),
destination_namespace: environment,
destination_server: "https://kubernetes.default.svc".to_string(),
}
}
pub fn destination_namespace(mut self, namespace: String) -> Self {
self.destination_namespace = namespace;
self
}
pub fn destination_server(mut self, server: String) -> Self {
self.destination_server = server;
self
}
}
#[derive(Debug, Clone)]
pub struct FluxHelmRelease {
pub name: String,
pub namespace: String,
pub chart_name: String,
pub chart_repo: String,
pub chart_version: Option<String>,
pub values: std::collections::HashMap<String, String>,
pub wait_for_jobs: bool,
pub disable_webhooks: bool,
pub force: bool,
}
impl FluxHelmRelease {
pub fn new(name: String, namespace: String, chart_name: String, chart_repo: String) -> Self {
Self {
name,
namespace,
chart_name,
chart_repo,
chart_version: None,
values: std::collections::HashMap::new(),
wait_for_jobs: true,
disable_webhooks: false,
force: false,
}
}
pub fn chart_version(mut self, version: String) -> Self {
self.chart_version = Some(version);
self
}
pub fn add_value(&mut self, key: String, value: String) {
self.values.insert(key, value);
}
pub fn wait_for_jobs(mut self, wait: bool) -> Self {
self.wait_for_jobs = wait;
self
}
pub fn disable_webhooks(mut self, disable: bool) -> Self {
self.disable_webhooks = disable;
self
}
pub fn force(mut self, force: bool) -> Self {
self.force = force;
self
}
}
#[derive(Debug, Clone)]
pub struct GitOpsSyncStatus {
pub success: bool,
pub message: String,
pub name: Option<String>,
pub release_name: Option<String>,
}
pub struct GitOpsManager {
tool: String,
pub applications: Vec<ArgoCDApplication>,
pub helm_releases: Vec<FluxHelmRelease>,
}
impl GitOpsManager {
pub fn new(tool: String) -> Self {
Self {
tool,
applications: Vec::new(),
helm_releases: Vec::new(),
}
}
pub fn add_application(&mut self, app: ArgoCDApplication) {
self.applications.push(app);
}
pub fn get_application(&self, name: &str) -> Option<&ArgoCDApplication> {
self.applications.iter().find(|app| app.name == name)
}
pub fn add_helm_release(&mut self, release: FluxHelmRelease) {
self.helm_releases.push(release);
}
pub fn get_helm_release(&self, name: &str) -> Option<&FluxHelmRelease> {
self.helm_releases
.iter()
.find(|release| release.name == name)
}
pub fn sync_application(&self, name: &str) -> Result<GitOpsSyncStatus, Error> {
if self.tool != "argocd" {
return Err(Error::InvalidTool {
expected: "argocd".to_string(),
actual: self.tool.clone(),
});
}
if let Some(app) = self.applications.iter().find(|a| a.name == name) {
Ok(GitOpsSyncStatus {
success: true,
message: format!(
"Application '{}' synced successfully to {}",
app.name, app.environment
),
name: Some(app.name.clone()),
release_name: None,
})
} else {
Err(Error::ApplicationNotFound {
name: name.to_string(),
})
}
}
pub fn sync_helm_release(&self, name: &str) -> Result<GitOpsSyncStatus, Error> {
if self.tool != "flux" {
return Err(Error::InvalidTool {
expected: "flux".to_string(),
actual: self.tool.clone(),
});
}
if let Some(release) = self.helm_releases.iter().find(|r| r.name == name) {
Ok(GitOpsSyncStatus {
success: true,
message: format!(
"Helm release '{}' synced successfully to namespace {}",
release.name, release.namespace
),
name: None,
release_name: Some(release.name.clone()),
})
} else {
Err(Error::HelmReleaseNotFound {
name: name.to_string(),
})
}
}
pub fn get_sync_status(&self) -> Result<Vec<GitOpsSyncStatus>, Error> {
let mut statuses = Vec::new();
if self.tool == "argocd" {
for app in &self.applications {
statuses.push(GitOpsSyncStatus {
success: true,
message: format!("Application '{}' is synced", app.name),
name: Some(app.name.clone()),
release_name: None,
});
}
} else if self.tool == "flux" {
for release in &self.helm_releases {
statuses.push(GitOpsSyncStatus {
success: true,
message: format!("Helm release '{}' is synced", release.name),
name: None,
release_name: Some(release.name.clone()),
});
}
}
Ok(statuses)
}
pub fn get_tool(&self) -> &str {
&self.tool
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Invalid GitOps tool: expected {expected}, got {actual}")]
InvalidTool { expected: String, actual: String },
#[error("Application not found: {name}")]
ApplicationNotFound { name: String },
#[error("Helm release not found: {name}")]
HelmReleaseNotFound { name: String },
#[error("GitOps error: {0}")]
GitOpsError(String),
#[error("Configuration error: {0}")]
ConfigurationError(String),
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn test_argocd_application_creation() {
let app: _ = ArgoCDApplication::new(
"amberjs-app".to_string(),
"production".to_string(),
"https://github.com/example/amberjs-manifests.git".to_string(),
"main".to_string(),
"/manifests".to_string(),
);
assert_eq!(app.name, "amberjs-app");
assert_eq!(app.environment, "production");
assert_eq!(
app.repo_url,
"https://github.com/example/amberjs-manifests.git"
);
assert_eq!(app.target_revision, "main");
assert_eq!(app.path, "/manifests");
assert!(app.sync_policy.automatic);
}
#[test]
fn test_flux_helm_release_creation() {
let release: _ = FluxHelmRelease::new(
"amberjs".to_string(),
"production".to_string(),
"amberjs".to_string(),
"https://helm.github.io/charts".to_string(),
);
assert_eq!(release.name, "amberjs");
assert_eq!(release.namespace, "production");
assert_eq!(release.chart_name, "amberjs");
assert_eq!(release.chart_repo, "https://helm.github.io/charts");
assert!(release.wait_for_jobs);
}
#[test]
fn test_gitops_manager() {
let mut manager = GitOpsManager::new("argocd".to_string());
let app: _ = ArgoCDApplication::new(
"test-app".to_string(),
"production".to_string(),
"https://github.com/test/repo.git".to_string(),
"main".to_string(),
"/manifests".to_string(),
);
manager.add_application(app);
assert_eq!(manager.applications.len(), 1);
let status: _ = manager.sync_application("test-app");
assert!(status.is_ok());
if let Ok(status) = status {
assert!(status.success);
assert!(status.name.is_some());
}
}
#[test]
fn test_gitops_manager_flux() {
let mut manager = GitOpsManager::new("flux".to_string());
let release: _ = FluxHelmRelease::new(
"amberjs".to_string(),
"production".to_string(),
"amberjs".to_string(),
"https://helm.github.io/charts".to_string(),
);
manager.add_helm_release(release);
assert_eq!(manager.helm_releases.len(), 1);
let status: _ = manager.sync_helm_release("amberjs");
assert!(status.is_ok());
if let Ok(status) = status {
assert!(status.success);
assert!(status.release_name.is_some());
}
}
#[test]
fn test_sync_status() {
let manager: _ = GitOpsManager::new("argocd".to_string());
let app: _ = ArgoCDApplication::new(
"test-app".to_string(),
"production".to_string(),
"https://github.com/test/repo.git".to_string(),
"main".to_string(),
"/manifests".to_string(),
);
let _: _ = app;
let _: _ = manager;
}
}
use std::collections::{BTreeMap, HashMap};