use std::sync::Arc;
use std::time::Duration;
use tokio::time::interval;
use tracing::{debug, error, info, warn};
use crate::db::projects as db_projects;
use crate::server::ecr::{EcrRepoManager, ECR_FINALIZER};
use crate::server::state::ControllerState;
pub struct EcrController {
state: Arc<ControllerState>,
manager: Arc<EcrRepoManager>,
provision_interval: Duration,
cleanup_interval: Duration,
drift_interval: Duration,
}
impl EcrController {
pub fn new(state: Arc<ControllerState>, manager: Arc<EcrRepoManager>) -> Self {
Self {
state,
manager,
provision_interval: Duration::from_secs(10),
cleanup_interval: Duration::from_secs(5),
drift_interval: Duration::from_secs(60),
}
}
pub fn start(self: Arc<Self>) {
let provision_self = Arc::clone(&self);
tokio::spawn(async move {
provision_self.provision_loop().await;
});
let cleanup_self = Arc::clone(&self);
tokio::spawn(async move {
cleanup_self.cleanup_loop().await;
});
let drift_self = Arc::clone(&self);
tokio::spawn(async move {
drift_self.drift_detection_loop().await;
});
}
async fn provision_loop(&self) {
info!("ECR provision loop started");
let mut ticker = interval(self.provision_interval);
loop {
ticker.tick().await;
if let Err(e) = self.provision_repositories().await {
error!("Error in ECR provision loop: {}", e);
}
}
}
async fn provision_repositories(&self) -> anyhow::Result<()> {
let projects = db_projects::list_active(&self.state.db_pool).await?;
for project in projects {
if project.finalizers.contains(&ECR_FINALIZER.to_string()) {
continue;
}
debug!("Provisioning ECR repository for project: {}", project.name);
match self.manager.create_repository(&project.name).await {
Ok(created) => {
if created {
info!("Created ECR repository for project: {}", project.name);
} else {
debug!(
"ECR repository already exists for project: {}",
project.name
);
}
db_projects::add_finalizer(&self.state.db_pool, project.id, ECR_FINALIZER)
.await?;
debug!("Added ECR finalizer to project: {}", project.name);
}
Err(e) => {
warn!(
"Failed to create ECR repository for project {}: {}",
project.name, e
);
}
}
}
Ok(())
}
async fn cleanup_loop(&self) {
info!("ECR cleanup loop started");
let mut ticker = interval(self.cleanup_interval);
loop {
ticker.tick().await;
if let Err(e) = self.cleanup_repositories().await {
error!("Error in ECR cleanup loop: {}", e);
}
}
}
async fn cleanup_repositories(&self) -> anyhow::Result<()> {
let projects =
db_projects::find_deleting_with_finalizer(&self.state.db_pool, ECR_FINALIZER, 10)
.await?;
for project in projects {
debug!("Cleaning up ECR repository for project: {}", project.name);
let cleanup_result = if self.manager.auto_remove() {
match self.manager.delete_repository(&project.name).await {
Ok(deleted) => {
if deleted {
info!("Deleted ECR repository for project: {}", project.name);
} else {
info!(
"ECR repository did not exist for project: {} (already deleted)",
project.name
);
}
Ok(())
}
Err(e) => Err(e),
}
} else {
match self.manager.tag_as_orphaned(&project.name).await {
Ok(tagged) => {
if tagged {
info!(
"Tagged ECR repository as orphaned for project: {}",
project.name
);
} else {
info!(
"ECR repository did not exist for project: {} (already deleted)",
project.name
);
}
Ok(())
}
Err(e) => Err(e),
}
};
match cleanup_result {
Ok(()) => {
db_projects::remove_finalizer(&self.state.db_pool, project.id, ECR_FINALIZER)
.await?;
info!(
"Removed ECR finalizer from project: {}, cleanup complete",
project.name
);
}
Err(e) => {
warn!(
"Failed to cleanup ECR repository for project {}: {}",
project.name, e
);
}
}
}
Ok(())
}
async fn drift_detection_loop(&self) {
info!("ECR drift detection loop started");
let mut ticker = interval(self.drift_interval);
loop {
ticker.tick().await;
if let Err(e) = self.detect_repository_drift().await {
error!("Error in ECR drift detection loop: {}", e);
}
}
}
async fn detect_repository_drift(&self) -> anyhow::Result<()> {
let projects = db_projects::list_active(&self.state.db_pool).await?;
for project in projects {
if !project.finalizers.contains(&ECR_FINALIZER.to_string()) {
continue;
}
match self.manager.repository_exists(&project.name).await {
Ok(exists) => {
if !exists {
warn!(
"ECR repository drift detected for project {}: repository missing but finalizer present",
project.name
);
db_projects::remove_finalizer(
&self.state.db_pool,
project.id,
ECR_FINALIZER,
)
.await?;
info!(
"Removed ECR finalizer from project {} to trigger repository recreation",
project.name
);
}
}
Err(e) => {
warn!(
"Failed to check ECR repository existence for project {}: {}",
project.name, e
);
}
}
}
Ok(())
}
}