use super::*;
impl DeployExecutor {
pub async fn run(self: Arc<Self>, deployment_id: String) {
info!(deployment_id = %deployment_id, "executor: iniciando");
if let Err(e) = self.execute(&deployment_id).await {
error!(deployment_id = %deployment_id, error = %e, "executor: falha fatal no deploy");
self.bus.publish(Event::Error {
code: "ExecutorFatal".into(),
message: format!("Falha crítica no deploy {}: {e}", {
let s = deployment_id
.find('_')
.map(|i| &deployment_id[i + 1..])
.unwrap_or(&deployment_id);
&s[..8.min(s.len())]
}),
});
}
info!(deployment_id = %deployment_id, "executor: encerrado");
}
async fn execute(&self, deployment_id: &str) -> Result<()> {
loop {
let deployment = self.load_deployment(deployment_id).await?;
info!(
deployment_id = %deployment_id,
state = deployment.state.label(),
"executor: estado atual"
);
if deployment.state.is_terminal() {
info!(
deployment_id = %deployment_id,
state = deployment.state.label(),
"executor: estado terminal, saindo do loop"
);
break;
}
let service = self
.load_service(&deployment.service_id)
.await?
.ok_or_else(|| anyhow!("service not found: {}", deployment.service_id))?;
info!(
deployment_id = %deployment_id,
service_id = %service.id,
service_name = %service.spec.name,
state = deployment.state.label(),
"executor: executando step"
);
let result = self.step(&deployment, &service).await;
match result {
Ok(next_state) => {
info!(
deployment_id = %deployment_id,
from = deployment.state.label(),
to = next_state.label(),
"executor: transição de estado"
);
self.transition(deployment_id, &deployment.state, next_state, None)
.await?;
}
Err(e) => {
warn!(
deployment_id = %deployment_id,
state = deployment.state.label(),
error = %e,
"executor: step falhou, iniciando rollback"
);
for line in failure_log_lines(deployment.state.label(), &e.to_string()) {
self.log_step(deployment_id, &service.id, &line).await;
}
self.transition(
deployment_id,
&deployment.state,
DeployState::RollingBack,
Some(e.to_string()),
)
.await?;
}
}
}
Ok(())
}
pub(super) async fn step(&self, dep: &Deployment, svc: &Service) -> Result<DeployState> {
match &dep.state {
DeployState::Pending => self.step_pending(dep, svc).await,
DeployState::PreDeployCheck => self.step_pre_deploy_check(dep, svc).await,
DeployState::ResolvingDeps => self.step_resolving_deps(dep, svc).await,
DeployState::PullingImage => self.step_pulling_image(dep, svc).await,
DeployState::CloningRepo => self.step_cloning_repo(dep, svc).await,
DeployState::BuildingImage => self.step_building_image(dep, svc).await,
DeployState::Staging => self.step_staging(dep, svc).await,
DeployState::HealthcheckPolling => self.step_healthcheck_polling(dep, svc).await,
DeployState::SwappingIn => self.step_swapping_in(dep, svc).await,
DeployState::Draining => self.step_draining(dep, svc).await,
DeployState::Promoting => self.step_promoting(dep, svc).await,
DeployState::RollingBack => self.step_rolling_back(dep, svc).await,
DeployState::ComposingUp => self.step_composing_up(dep, svc).await,
other => Err(anyhow!("unhandled state: {:?}", other)),
}
}
}
pub(super) fn failure_log_lines(state_label: &str, err: &str) -> Vec<String> {
let mut linhas = err
.lines()
.map(str::trim_end)
.filter(|l| !l.trim().is_empty());
let Some(primeira) = linhas.next() else {
return vec![format!("==> Erro em [{state_label}]: falha sem mensagem")];
};
let mut out = vec![format!("==> Erro em [{state_label}]: {primeira}")];
out.extend(linhas.map(|l| format!(" {l}")));
out
}