use axum::http::StatusCode;
use chrono::Utc;
use tracing::error;
use crate::db::deployments as db_deployments;
use crate::db::env_vars as db_env_vars;
use crate::db::models::{Deployment, Project};
use crate::server::extensions::InjectedEnvVarValue;
use crate::server::state::AppState;
pub fn generate_deployment_id() -> String {
Utc::now().format("%Y%m%d-%H%M%S").to_string()
}
pub async fn get_deployment_image_tag(
state: &AppState,
deployment: &Deployment,
project: &Project,
) -> String {
if let Some(ref digest) = deployment.image_digest {
return digest.clone();
}
let deployment_id_for_tag =
if let Some(source_deployment_id) = deployment.rolled_back_from_deployment_id {
match db_deployments::find_by_id(&state.db_pool, source_deployment_id).await {
Ok(Some(source_deployment)) => source_deployment.deployment_id,
Ok(None) => {
tracing::warn!(
"Rollback deployment {} references non-existent source deployment {}",
deployment.deployment_id,
source_deployment_id
);
deployment.deployment_id.clone()
}
Err(e) => {
tracing::error!(
"Failed to fetch source deployment {} for rollback {}: {}",
source_deployment_id,
deployment.deployment_id,
e
);
deployment.deployment_id.clone()
}
}
} else {
deployment.deployment_id.clone()
};
let registry_url = state.registry_provider.registry_url();
format!(
"{}/{}:{}",
registry_url.trim_end_matches('/'),
project.name,
deployment_id_for_tag
)
}
pub async fn create_deployment_with_hooks(
state: &AppState,
params: db_deployments::CreateDeploymentParams<'_>,
project: &Project,
) -> Result<Deployment, (StatusCode, String)> {
let deployment_group = params.deployment_group.to_string();
let deployment = db_deployments::create(&state.db_pool, params)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Failed to create deployment: {}", e),
)
})?;
for (_, extension) in state.extension_registry.iter() {
let vars = match extension
.before_deployment(project.id, &deployment_group)
.await
{
Ok(vars) => vars,
Err(e) => {
error!(
"Extension type '{}' before_deployment hook failed: {:?}",
extension.extension_type(),
e
);
let error_msg = format!(
"Extension type '{}' failed: {}",
extension.extension_type(),
e
);
if let Err(mark_err) =
db_deployments::mark_failed(&state.db_pool, deployment.id, &error_msg).await
{
error!(
"Failed to mark deployment as failed after extension error: {:?}",
mark_err
);
}
return Err((StatusCode::INTERNAL_SERVER_ERROR, error_msg));
}
};
for var in vars {
let (value, is_secret, is_protected) = match var.value {
InjectedEnvVarValue::Plain(v) => (v, false, false),
InjectedEnvVarValue::Secret { encrypted, .. } => (encrypted, true, false),
InjectedEnvVarValue::Protected { encrypted, .. } => (encrypted, true, true),
};
if let Err(e) = db_env_vars::upsert_deployment_env_var(
&state.db_pool,
deployment.id,
&var.key,
&value,
is_secret,
is_protected,
)
.await
{
error!(
"Failed to write env var '{}' for deployment {}: {:?}",
var.key, deployment.deployment_id, e
);
let error_msg = format!("Failed to write env var '{}': {}", var.key, e);
if let Err(mark_err) =
db_deployments::mark_failed(&state.db_pool, deployment.id, &error_msg).await
{
error!(
"Failed to mark deployment as failed after env var write error: {:?}",
mark_err
);
}
return Err((StatusCode::INTERNAL_SERVER_ERROR, error_msg));
}
}
}
Ok(deployment)
}