diff --git a/dozer-cli/src/cli/cloud.rs b/dozer-cli/src/cli/cloud.rs index 705a698af4..a9bce50e12 100644 --- a/dozer-cli/src/cli/cloud.rs +++ b/dozer-cli/src/cli/cloud.rs @@ -93,9 +93,9 @@ pub struct LogCommandArgs { #[arg(short, long)] pub follow: bool, - /// The deployment to inspect + /// The version to inspect #[arg(short, long)] - pub deployment: Option, + pub version: Option, /// Ignore app logs #[arg(long, default_value = "false", action=ArgAction::SetTrue)] @@ -131,11 +131,6 @@ pub struct ListCommandArgs { #[derive(Debug, Clone, Subcommand)] pub enum VersionCommand { - /// Inspects the status of a version, compared to the current version if it's not current. - Status { - /// The version to inspect - version: u32, - }, /// Sets a version as the "current" version of the application /// /// Current version of an application can be visited without the "/v" prefix. diff --git a/dozer-cli/src/simple/cloud/mod.rs b/dozer-cli/src/simple/cloud/mod.rs index b76b2213a2..c8ea2673dc 100644 --- a/dozer-cli/src/simple/cloud/mod.rs +++ b/dozer-cli/src/simple/cloud/mod.rs @@ -2,4 +2,3 @@ pub mod deployer; pub mod login; pub mod monitor; pub mod progress_printer; -pub mod version; diff --git a/dozer-cli/src/simple/cloud/version.rs b/dozer-cli/src/simple/cloud/version.rs deleted file mode 100644 index 918deba7c7..0000000000 --- a/dozer-cli/src/simple/cloud/version.rs +++ /dev/null @@ -1,192 +0,0 @@ -use std::{collections::HashMap, fmt::Write}; - -use dozer_api::rest::DOZER_SERVER_NAME_HEADER; -use dozer_cache::Phase; -use dozer_types::{ - prettytable::{row, table}, - serde_json, -}; - -use crate::errors::CloudError; - -#[derive(Debug)] -pub struct PathStatus { - count: usize, - phase: Phase, -} - -#[derive(Debug, Default)] -pub struct ServerStatus { - paths: HashMap, -} - -#[derive(Debug)] -pub struct VersionStatus { - servers: Vec, -} - -pub async fn get_version_status( - endpoint: &str, - version: u32, - api_available: i32, -) -> Result { - let (clients, paths) = probe_dozer_servers(endpoint, version, api_available).await?; - - let mut servers = vec![]; - for client in clients { - let mut server_status = ServerStatus::default(); - for path in &paths { - let count = client - .post(format!("{}/v{}{}/count", endpoint, version, path)) - .send() - .await? - .error_for_status()?; - let count = count.json::().await?; - - let phase = client - .post(format!("{}/v{}{}/phase", endpoint, version, path)) - .send() - .await? - .error_for_status()?; - let phase = phase.json::().await?; - - server_status - .paths - .insert(path.clone(), PathStatus { count, phase }); - } - servers.push(server_status) - } - - Ok(VersionStatus { servers }) -} - -/// Probe the servers to get a list of clients with sticky session cookie, and all the paths available. -async fn probe_dozer_servers( - endpoint: &str, - version: u32, - count_hint: i32, -) -> Result<(Vec, Vec), CloudError> { - let mut clients = HashMap::, reqwest::Client>::default(); - let mut paths = vec![]; - - // Try to visit the version endpoint many times to cover all the servers. - for _ in 0..count_hint * 5 { - let client = reqwest::Client::builder().cookie_store(true).build()?; - let response = client - .get(format!("{}/v{}/", endpoint, version)) - .send() - .await? - .error_for_status()?; - let server_name = response - .headers() - .get(DOZER_SERVER_NAME_HEADER) - .ok_or(CloudError::MissingResponseHeader)? - .as_bytes(); - - if clients.contains_key(server_name) { - continue; - } - clients.insert(server_name.to_vec(), client); - - if paths.is_empty() { - paths = response.json::>().await?; - } - } - - Ok((clients.into_values().collect(), paths)) -} - -pub fn version_status_table(status: &Result) -> String { - if let Ok(status) = status { - let mut table = table!(); - for server in &status.servers { - let mut server_table = table!(); - for (path, PathStatus { count, phase }) in &server.paths { - let phase = serde_json::to_string(phase).expect("Should always succeed"); - server_table.add_row(row![path, count, phase]); - } - table.add_row(row![server_table]); - } - table.to_string() - } else { - "Unavailable".to_string() - } -} - -pub fn version_string(version: u32, is_current: bool, aliases: &HashMap) -> String { - let aliases: Vec<&str> = aliases - .iter() - .filter_map(move |(alias, alias_version)| { - (*alias_version == version).then_some(alias.as_str()) - }) - .collect(); - let mut s = format!("v{version}"); - if is_current { - write!(s, " (current)").unwrap(); - }; - if !aliases.is_empty() { - write!(s, " [{}]", aliases.join(", ")).unwrap(); - }; - s -} - -pub fn version_is_up_to_date( - status: &Result, - current_status: &Result, -) -> bool { - if let Ok(status) = status { - if let Ok(current_status) = current_status { - version_is_up_to_date_impl(status, current_status) - } else { - true - } - } else { - false - } -} - -/// We say a version is up to date if any server of this version are up to date with any server of current version. -fn version_is_up_to_date_impl(status: &VersionStatus, current_status: &VersionStatus) -> bool { - for server in &status.servers { - for current_server in ¤t_status.servers { - if server_is_up_to_date(server, current_server) { - return true; - } - } - } - true -} - -/// We say a server is up to date if all paths are up to date. -fn server_is_up_to_date(status: &ServerStatus, current_status: &ServerStatus) -> bool { - for (path, status) in &status.paths { - if !path_is_up_to_date(status, current_status.paths.get(path)) { - return false; - } - } - true -} - -fn path_is_up_to_date(status: &PathStatus, current_status: Option<&PathStatus>) -> bool { - if let Some(current_status) = current_status { - status.phase == Phase::Streaming - || (current_status.phase == Phase::Snapshotting && status.count >= current_status.count) - } else { - true - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_version_string() { - let mut aliases = HashMap::new(); - aliases.insert("alias1".to_owned(), 1); - assert_eq!(&version_string(2, false, &aliases), "v2"); - assert_eq!(&version_string(2, true, &aliases), "v2 (current)"); - assert_eq!(&version_string(1, false, &aliases), "v1 [alias1]"); - assert_eq!(&version_string(1, true, &aliases), "v1 (current) [alias1]"); - } -} diff --git a/dozer-cli/src/simple/cloud_orchestrator.rs b/dozer-cli/src/simple/cloud_orchestrator.rs index 72acf02e6d..55f6254899 100644 --- a/dozer-cli/src/simple/cloud_orchestrator.rs +++ b/dozer-cli/src/simple/cloud_orchestrator.rs @@ -12,7 +12,6 @@ use crate::errors::{ use crate::simple::cloud::deployer::deploy_app; use crate::simple::cloud::login::CredentialInfo; use crate::simple::cloud::monitor::monitor_app; -use crate::simple::cloud::version::version_string; use crate::simple::token_layer::TokenLayer; use crate::simple::SimpleOrchestrator; use crate::CloudOrchestrator; @@ -21,11 +20,11 @@ use dozer_types::grpc_types::api_explorer::api_explorer_service_client::ApiExplo use dozer_types::grpc_types::api_explorer::GetApiTokenRequest; use dozer_types::grpc_types::cloud::{ dozer_cloud_client::DozerCloudClient, CreateSecretRequest, DeleteAppRequest, - DeleteSecretRequest, GetEndpointCommandsSamplesRequest, GetSecretRequest, GetStatusRequest, - ListAppRequest, ListSecretsRequest, LogMessageRequest, UpdateSecretRequest, + DeleteSecretRequest, GetEndpointCommandsSamplesRequest, GetSecretRequest, ListAppRequest, + ListSecretsRequest, LogMessageRequest, UpdateSecretRequest, }; use dozer_types::grpc_types::cloud::{ - CreateAppRequest, DeploymentInfo, DeploymentStatusWithHealth, File, ListDeploymentRequest, + CreateAppRequest, DeploymentInfo, DeploymentStatus, File, GetAppRequest, ListDeploymentRequest, RmAliasRequest, SetAliasRequest, SetCurrentVersionRequest, }; use dozer_types::log::info; @@ -36,7 +35,6 @@ use tonic::transport::Endpoint; use tower::ServiceBuilder; use super::cloud::login::LoginSvc; -use super::cloud::version::{get_version_status, version_is_up_to_date, version_status_table}; async fn establish_cloud_service_channel( cloud: &Cloud, cloud_config: &dozer_types::models::cloud::Cloud, @@ -231,67 +229,40 @@ impl CloudOrchestrator for SimpleOrchestrator { self.runtime.block_on(async move { let mut client = get_cloud_client(&cloud, cloud_config).await?; let response = client - .get_status(GetStatusRequest { app_id }) + .get_application(GetAppRequest { app_id }) .await .map_err(map_tonic_error)? .into_inner(); let mut table = table!(); + table.set_titles(row!["Deployment", "Version", "Status"]); - table.add_row(row!["Api endpoint", response.data_endpoint,]); - - let mut deployment_table = table!(); - deployment_table.set_titles(row![ - "Deployment", - "App", - "Api", - "Version", - "Phase", - "Error" - ]); - - for status in response.deployments.iter() { - let deployment = status.deployment.as_ref().expect("deployment is expected"); - fn mark(status: bool) -> &'static str { - if status { - "🟢" - } else { - "🟠" + for deployment in response.deployments.iter() { + fn deployment_status(status: i32) -> &'static str { + match status { + _ if status == DeploymentStatus::Pending as i32 => { + DeploymentStatus::Pending.as_str_name() + } + _ if status == DeploymentStatus::Running as i32 => { + DeploymentStatus::Running.as_str_name() + } + _ if status == DeploymentStatus::Success as i32 => { + DeploymentStatus::Success.as_str_name() + } + _ if status == DeploymentStatus::Failed as i32 => { + DeploymentStatus::Failed.as_str_name() + } + _ => "UNRECOGNIZED", } } - let found = response - .versions - .iter() - .find_map(|(version, version_deployment)| { - (*version_deployment == deployment.deployment).then_some(*version) - }); - - let version = found - .map(|version| { - version_string( - version, - Some(version) == response.current_version, - &response.aliases, - ) - }) - .unwrap_or_default(); - deployment_table.add_row(row![ - deployment.deployment, - format!("Deployment Status: {:?}", deployment.status), - format!("Version: {}", version), + table.add_row(row![ + deployment.deployment_id, + deployment.version, + deployment_status(deployment.status), ]); - for r in status.resources.iter() { - deployment_table.add_row(row![ - "", - format!("{}: {}", r.typ, mark(r.available == r.desired)), - format!("{}/{}", r.available.unwrap_or(0), r.desired.unwrap_or(0)), - ]); - } } - table.add_row(row!["Deployments", deployment_table]); - table.printstd(); Ok::<(), CloudError>(()) })?; @@ -321,18 +292,15 @@ impl CloudOrchestrator for SimpleOrchestrator { .map_err(map_tonic_error)? .into_inner(); - // Show log of the latest deployment for now. - let Some(deployment) = logs - .deployment - .or_else(|| latest_deployment(&res.deployments)) - else { - info!("No deployments found"); + // Show log of the latest version for now. + let Some(version) = logs.version.or_else(|| latest_version(&res.deployments)) else { + info!("No active version found"); return Ok(()); }; let mut response = client .on_log_message(LogMessageRequest { app_id, - deployment, + version, follow: logs.follow, include_build: !logs.ignore_build, include_app: !logs.ignore_app, @@ -531,68 +499,6 @@ impl SimpleOrchestrator { .set_current_version(SetCurrentVersionRequest { app_id, version }) .await?; } - VersionCommand::Status { version } => { - let status = client - .get_status(GetStatusRequest { app_id }) - .await - .map_err(map_tonic_error)? - .into_inner(); - let Some(deployment) = status.versions.get(&version) else { - info!("Version {} does not exist", version); - return Ok(()); - }; - let api_available = get_api_available(&status.deployments, *deployment); - - let version_status = - get_version_status(&status.data_endpoint, version, api_available).await; - let mut table = table!(); - - if let Some(current_version) = status.current_version { - if current_version != version { - let current_api_available = get_api_available( - &status.deployments, - status.versions[¤t_version], - ); - - table.add_row(row![ - version_string(version, false, &status.aliases), - version_status_table(&version_status) - ]); - - let current_version_status = get_version_status( - &status.data_endpoint, - current_version, - current_api_available, - ) - .await; - table.add_row(row![ - version_string(current_version, true, &Default::default()), - version_status_table(¤t_version_status) - ]); - - table.printstd(); - - if version_is_up_to_date(&version_status, ¤t_version_status) { - info!("Version {} is up to date", version); - } else { - info!("Version {} is not up to date", version); - } - } else { - table.add_row(row![ - version_string(version, true, &status.aliases), - version_status_table(&version_status) - ]); - table.printstd(); - } - } else { - table.add_row(row![ - version_string(version, false, &status.aliases), - version_status_table(&version_status) - ]); - table.printstd(); - info!("No current version"); - }; - } VersionCommand::Alias { alias, version } => { client .set_alias(SetAliasRequest { @@ -672,27 +578,6 @@ impl SimpleOrchestrator { } } -fn latest_deployment(deployments: &[DeploymentInfo]) -> Option { - deployments.iter().map(|status| status.deployment).max() -} - -fn get_api_available(deployments: &[DeploymentStatusWithHealth], deployment: u32) -> i32 { - let info = deployments - .iter() - .find(|status| { - status - .deployment - .as_ref() - .expect("deployment is expected") - .deployment - == deployment - }) - .expect("Deployment should be found in deployments"); - - info.resources - .clone() - .into_iter() - .find(|r| r.typ == "api") - .and_then(|r| r.available) - .unwrap_or(1) +fn latest_version(deployments: &[DeploymentInfo]) -> Option { + deployments.iter().map(|status| status.version).max() } diff --git a/dozer-types/protos/cloud.proto b/dozer-types/protos/cloud.proto index a11c67fbfb..25b8ba69b2 100644 --- a/dozer-types/protos/cloud.proto +++ b/dozer-types/protos/cloud.proto @@ -24,8 +24,6 @@ service DozerCloud { rpc update_application(UpdateAppRequest) returns (AppResponse); rpc delete_application(DeleteAppRequest) returns (DeleteAppResponse); rpc get_application(GetAppRequest) returns (AppResponse); - rpc stop_dozer(StopRequest) returns (StopResponse); - rpc get_status(GetStatusRequest) returns (GetStatusResponse); rpc list_deployments(ListDeploymentRequest) returns (ListDeploymentResponse); rpc list_versions(ListVersionsRequest) returns (ListVersionsResponse); rpc set_alias(SetAliasRequest) returns (SetAliasResponse); @@ -131,22 +129,7 @@ message DeleteAppRequest { } message DeleteAppResponse { bool success = 1; } -message GetAppRequest { optional string app_id = 1; } - -message GetStatusRequest{ - string app_id = 1; -} -message GetStatusResponse { - string data_endpoint = 1; - repeated DeploymentStatusWithHealth deployments = 2; - map versions = 4; - optional uint32 current_version = 5; - map aliases = 6; -} -message DeploymentStatusWithHealth { - DeploymentInfo deployment = 1; - repeated DeploymentResource resources = 2; -} +message GetAppRequest { string app_id = 1; } message GetResourcesRequest { string app_id = 1; @@ -154,13 +137,15 @@ message GetResourcesRequest { optional uint32 version = 2; } message ResourcesResponse { - repeated DeploymentResource resources = 1; + uint32 version = 1; + DeploymentResource app = 2; + DeploymentResource api = 3; + DeploymentStatus deployment_status = 4; } message DeploymentResource { string name = 1; - // api, app - string typ = 2; + string created_at = 3; optional int32 desired = 4; @@ -183,12 +168,12 @@ enum DeploymentStatus { message ListVersionsRequest { string app_id = 1; } message ListVersionsResponse { optional uint32 current_version = 4; - map versions = 3; + repeated uint32 versions = 3; } message DeploymentInfo { string deployment_id = 1; - uint32 deployment = 2; + uint32 version = 2; DeploymentStatus status = 3; optional google.protobuf.Timestamp created_at = 4; optional google.protobuf.Timestamp updated_at = 5; @@ -291,7 +276,7 @@ message FileInfo { message LogMessageRequest { string app_id = 1; - uint32 deployment = 2; + uint32 version = 2; bool follow = 3; bool include_build = 4; bool include_app = 5;