use std::time::Duration; use kenshell::proto::v1 as pb; use tokio::time::sleep; use crate::share::errors::GitError; pub(crate) fn rpc_error(status: tonic::Status) -> GitError { match status.code() { tonic::Code::NotFound => GitError::NotFound, tonic::Code::PermissionDenied => GitError::Forbidden, tonic::Code::Unauthenticated => GitError::Unauthorized, tonic::Code::AlreadyExists | tonic::Code::Aborted | tonic::Code::FailedPrecondition => { GitError::Conflict(status.message().to_string()) } tonic::Code::InvalidArgument => GitError::InvalidArgument(status.message().to_string()), tonic::Code::Unimplemented => { GitError::Unimplemented("operation is not supported by KenShell") } _ => GitError::Internal(status.message().to_string()), } } pub(crate) async fn wait_job( ks: &kenshell::KenShellClient, job_id: &str, timeout: Duration, ) -> Result { let deadline = tokio::time::Instant::now() + timeout; let mut client = ks.job(); loop { let job = client .get_job(pb::GetJobRequest { id: job_id.to_string(), }) .await .map_err(rpc_error)? .into_inner() .job .ok_or_else(|| GitError::Internal("job missing".into()))?; match pb::JobState::try_from(job.state).unwrap_or(pb::JobState::Unspecified) { pb::JobState::Succeeded => return Ok(job), pb::JobState::Failed | pb::JobState::Canceled => { return Err(GitError::Internal(if job.error_message.is_empty() { "job failed".to_string() } else { job.error_message })); } _ => {} } if tokio::time::Instant::now() >= deadline { return Err(GitError::Internal("job timed out".into())); } sleep(Duration::from_millis(400)).await; } } impl crate::service::GitService { pub(crate) fn job_timeout(&self) -> Duration { Duration::from_secs(self.state.config.git.git_command_timeout_secs.max(30)) } }