diff --git a/dozer-cli/src/cloud/client.rs b/dozer-cli/src/cloud/client.rs index e52911c3fc..2392a9cde5 100644 --- a/dozer-cli/src/cloud/client.rs +++ b/dozer-cli/src/cloud/client.rs @@ -1,7 +1,7 @@ use crate::cli::cloud::{ Cloud, DeployCommandArgs, ListCommandArgs, LogCommandArgs, SecretsCommand, VersionCommand, }; -use crate::cli::{get_base_dir, init_dozer}; +use crate::cli::get_base_dir; use crate::cloud::cloud_app_context::CloudAppContext; use crate::cloud::cloud_helper::list_files; @@ -13,7 +13,7 @@ use crate::cloud::DozerGrpcCloudClient; use crate::console_helper::{get_colored_text, PURPLE}; use crate::errors::OrchestrationError::FailedToReadOrganisationName; use crate::errors::{ - map_tonic_error, CliError, CloudError, CloudLoginError, ConfigCombineError, OrchestrationError, + map_tonic_error, CliError, CloudContextError, CloudError, CloudLoginError, OrchestrationError, }; use crate::simple::orchestrator::lockfile_path; use dozer_types::constants::{DEFAULT_CLOUD_TARGET_URL, LOCK_FILE}; @@ -34,17 +34,16 @@ use dozer_types::prettytable::{row, table}; use futures::{select, FutureExt, StreamExt}; use std::io; use std::sync::Arc; -use tokio::runtime::Runtime; use tonic::transport::Endpoint; use tower::ServiceBuilder; use super::login::LoginSvc; async fn establish_cloud_service_channel( cloud: &Cloud, - cloud_config: &dozer_types::models::cloud::Cloud, + cloud_config: Option, ) -> Result { - let profile_name = match &cloud.profile { - None => cloud_config.profile.clone(), + let profile_name = match cloud.profile.as_ref() { + None => cloud_config.as_ref().and_then(|f| f.profile.clone()), Some(_) => cloud.profile.clone(), }; let credential = CredentialInfo::load(profile_name)?; @@ -64,7 +63,7 @@ async fn establish_cloud_service_channel( pub async fn get_grpc_cloud_client( cloud: &Cloud, - cloud_config: &dozer_types::models::cloud::Cloud, + cloud_config: Option, ) -> Result, CloudError> { let client = DozerCloudClient::new(establish_cloud_service_channel(cloud, cloud_config).await?); Ok(client) @@ -72,7 +71,7 @@ pub async fn get_grpc_cloud_client( pub async fn get_explorer_client( cloud: &Cloud, - cloud_config: &dozer_types::models::cloud::Cloud, + cloud_config: Option, ) -> Result, CloudError> { let client = ApiExplorerServiceClient::new(establish_cloud_service_channel(cloud, cloud_config).await?); @@ -80,14 +79,32 @@ pub async fn get_explorer_client( } pub struct CloudClient { - config: Config, + config: Option, runtime: Arc, } impl CloudClient { - pub fn new(config: Config, runtime: Arc) -> Self { + pub fn new(config: Option, runtime: Arc) -> Self { Self { config, runtime } } + + pub fn get_app_id(&self) -> Option<(String, bool)> { + // Get app_id from command line argument if there, otherwise take it from the cloud config file + // if the app_id is from the cloud config file then set `from_cloud_file` to true and use it later + // to perform cleanup in case of delete and other operations + + self.config.as_ref().and_then(|config| { + config.cloud.app_id.clone().map(|app_id| (app_id, true)).or( + CloudAppContext::get_app_id(&config.cloud) + .ok() + .map(|app_id| (app_id, false)), + ) + }) + } + + fn get_cloud_config(&self) -> Option { + self.config.as_ref().map(|config| config.cloud.clone()) + } } impl DozerGrpcCloudClient for CloudClient { @@ -98,15 +115,12 @@ impl DozerGrpcCloudClient for CloudClient { deploy: DeployCommandArgs, config_paths: Vec, ) -> Result<(), OrchestrationError> { - let app_id = cloud - .app_id - .clone() - .or(CloudAppContext::get_app_id(&self.config.cloud).ok()); + let app_id = self.get_app_id().map(|a| a.0); let base_dir = get_base_dir()?; let lockfile_path = lockfile_path(base_dir); self.runtime.clone().block_on(async move { - let mut client = get_grpc_cloud_client(&cloud, &self.config.cloud).await?; + let mut client = get_grpc_cloud_client(&cloud, self.get_cloud_config()).await?; let mut files = list_files(config_paths)?; if deploy.locked { let lockfile_contents = tokio::fs::read_to_string(lockfile_path) @@ -140,23 +154,38 @@ impl DozerGrpcCloudClient for CloudClient { })?; Ok(()) } + fn create( + &mut self, + cloud: Cloud, + config_paths: Vec, + ) -> Result<(), OrchestrationError> { + let cloud_config = self.get_cloud_config(); + let app_id = self.get_app_id().map(|a| a.0); - fn delete(&mut self, cloud: Cloud) -> Result<(), OrchestrationError> { - // Get app_id from command line argument if there, otherwise take it from the cloud config file - // if the app_id is from the cloud config file then set `delete_cloud_file` to true and use it later - // to delete the file after deleting the app - - let (app_id, delete_cloud_file) = if let Some(app_id) = cloud.app_id.clone() { - // if the app_id on command line is equal to the one in the cloud config file then file can be deleted - if app_id == CloudAppContext::get_app_id(&self.config.cloud)? { - (app_id, true) - } else { - (app_id, false) - } - } else { - (CloudAppContext::get_app_id(&self.config.cloud)?, true) + if let Some(app_id) = app_id { + return Err(CloudContextError::AppIdAlreadyExists(app_id).into()); }; + self.runtime.block_on(async move { + let mut client = get_grpc_cloud_client(&cloud, cloud_config).await?; + let files = list_files(config_paths)?; + let response = client + .create_application(CreateAppRequest { files }) + .await + .map_err(map_tonic_error)? + .into_inner(); + + CloudAppContext::save_app_id(response.app_id.clone())?; + info!("Application created with id {}", response.app_id); + Ok::<(), OrchestrationError>(()) + }) + } + + fn delete(&mut self, cloud: Cloud) -> Result<(), OrchestrationError> { + let (app_id, delete_cloud_file) = self + .get_app_id() + .ok_or_else(|| CloudContextError::AppIdNotFound)?; + let mut double_check = String::new(); println!("Are you sure to delete the application {}? (y/N)", app_id); io::stdin() @@ -171,7 +200,7 @@ impl DozerGrpcCloudClient for CloudClient { return Ok(()); } - let cloud_config = &self.config.cloud; + let cloud_config = self.get_cloud_config(); self.runtime.block_on(async move { let mut client = get_grpc_cloud_client(&cloud, cloud_config).await?; @@ -198,7 +227,7 @@ impl DozerGrpcCloudClient for CloudClient { } fn list(&mut self, cloud: Cloud, list: ListCommandArgs) -> Result<(), OrchestrationError> { - let cloud_config = &self.config.cloud; + let cloud_config = self.get_cloud_config(); self.runtime.block_on(async move { let mut client = get_grpc_cloud_client(&cloud, cloud_config).await?; let response = client @@ -236,11 +265,11 @@ impl DozerGrpcCloudClient for CloudClient { } fn status(&mut self, cloud: Cloud) -> Result<(), OrchestrationError> { - let app_id = cloud - .app_id - .clone() - .unwrap_or(CloudAppContext::get_app_id(&self.config.cloud)?); - let cloud_config = &self.config.cloud; + let app_id = self + .get_app_id() + .map(|a| a.0) + .ok_or_else(|| CloudContextError::AppIdNotFound)?; + let cloud_config = self.get_cloud_config(); self.runtime.block_on(async move { let mut client = get_grpc_cloud_client(&cloud, cloud_config).await?; let response = client @@ -286,16 +315,21 @@ impl DozerGrpcCloudClient for CloudClient { } fn monitor(&mut self, cloud: Cloud) -> Result<(), OrchestrationError> { - monitor_app(&cloud, &self.config.cloud, self.runtime.clone()) + let cloud_config = self.get_cloud_config(); + let app_id = self + .get_app_id() + .map(|a| a.0) + .ok_or_else(|| CloudContextError::AppIdNotFound)?; + monitor_app(&cloud, app_id, cloud_config, self.runtime.clone()) .map_err(crate::errors::OrchestrationError::CloudError) } fn trace_logs(&mut self, cloud: Cloud, logs: LogCommandArgs) -> Result<(), OrchestrationError> { - let app_id = cloud - .app_id - .clone() - .unwrap_or(CloudAppContext::get_app_id(&self.config.cloud)?); - let cloud_config = &self.config.cloud; + let cloud_config = self.get_cloud_config(); + let app_id = self + .get_app_id() + .map(|a| a.0) + .ok_or_else(|| CloudContextError::AppIdNotFound)?; self.runtime.block_on(async move { let mut client = get_grpc_cloud_client(&cloud, cloud_config).await?; @@ -352,7 +386,7 @@ impl DozerGrpcCloudClient for CloudClient { } fn login( - runtime: Arc, + &self, cloud: Cloud, organisation_slug: Option, profile: Option, @@ -374,7 +408,7 @@ impl DozerGrpcCloudClient for CloudClient { Some(name) => name, }; - runtime.block_on(async move { + self.runtime.block_on(async move { let login_svc = LoginSvc::new( organisation_slug, cloud @@ -393,42 +427,15 @@ impl DozerGrpcCloudClient for CloudClient { cloud: Cloud, command: SecretsCommand, ) -> Result<(), OrchestrationError> { - let app_id_result = cloud - .app_id - .clone() - .map_or(CloudAppContext::get_app_id(&self.config.cloud), Ok); - - let config = self.config.clone(); - let cloud_config = &self.config.cloud; + let cloud_config = self.get_cloud_config(); + let app_id = self + .get_app_id() + .map(|a| a.0) + .ok_or_else(|| CloudContextError::AppIdNotFound)?; self.runtime.block_on(async move { let mut client = get_grpc_cloud_client(&cloud, cloud_config).await?; - let app_id = match app_id_result { - Ok(id) => Ok(id), - Err(_e) if matches!(command, SecretsCommand::Create { .. }) => { - let config_content = - dozer_types::serde_yaml::to_string(&config).map_err(|e| { - CloudError::ConfigCombineError(ConfigCombineError::ParseConfig(e)) - })?; - - let response = client - .create_application(CreateAppRequest { - files: vec![File { - name: "dozer.yaml".to_string(), - content: config_content, - }], - }) - .await - .map_err(map_tonic_error)? - .into_inner(); - - CloudAppContext::save_app_id(response.app_id.clone())?; - Ok(response.app_id) - } - Err(e) => Err(e), - }?; - match command { SecretsCommand::Create { name, value } => { client @@ -501,12 +508,12 @@ impl CloudClient { cloud: Cloud, version: VersionCommand, ) -> Result<(), OrchestrationError> { - let app_id = cloud - .app_id - .clone() - .unwrap_or(CloudAppContext::get_app_id(&self.config.cloud)?); + let cloud_config = self.get_cloud_config(); + let app_id = self + .get_app_id() + .map(|a| a.0) + .ok_or_else(|| CloudContextError::AppIdNotFound)?; - let cloud_config = &self.config.cloud; self.runtime.block_on(async move { let mut client = get_grpc_cloud_client(&cloud, cloud_config).await?; @@ -545,13 +552,13 @@ impl CloudClient { cloud: Cloud, endpoint: Option, ) -> Result<(), OrchestrationError> { - let app_id = cloud - .app_id - .clone() - .unwrap_or(CloudAppContext::get_app_id(&self.config.cloud)?); - let cloud_config = &self.config.cloud; + let cloud_config = self.get_cloud_config(); + let app_id = self + .get_app_id() + .map(|a| a.0) + .ok_or_else(|| CloudContextError::AppIdNotFound)?; self.runtime.block_on(async move { - let mut client = get_grpc_cloud_client(&cloud, cloud_config).await?; + let mut client = get_grpc_cloud_client(&cloud, cloud_config.clone()).await?; let mut explorer_client = get_explorer_client(&cloud, cloud_config).await?; let response = client diff --git a/dozer-cli/src/cloud/mod.rs b/dozer-cli/src/cloud/mod.rs index 588ea092a2..e26a8901ad 100644 --- a/dozer-cli/src/cloud/mod.rs +++ b/dozer-cli/src/cloud/mod.rs @@ -12,8 +12,6 @@ pub mod monitor; pub mod progress_printer; mod token_layer; pub use client::CloudClient; -use tokio::runtime::Runtime; - pub trait DozerGrpcCloudClient { fn deploy( &mut self, @@ -21,13 +19,15 @@ pub trait DozerGrpcCloudClient { deploy: DeployCommandArgs, config_paths: Vec, ) -> Result<(), OrchestrationError>; + fn create(&mut self, cloud: Cloud, config_paths: Vec) + -> Result<(), OrchestrationError>; fn delete(&mut self, cloud: Cloud) -> Result<(), OrchestrationError>; fn list(&mut self, cloud: Cloud, list: ListCommandArgs) -> Result<(), OrchestrationError>; fn status(&mut self, cloud: Cloud) -> Result<(), OrchestrationError>; fn monitor(&mut self, cloud: Cloud) -> Result<(), OrchestrationError>; fn trace_logs(&mut self, cloud: Cloud, logs: LogCommandArgs) -> Result<(), OrchestrationError>; fn login( - runtime: std::sync::Arc, + &self, cloud: Cloud, organisation_slug: Option, profile: Option, diff --git a/dozer-cli/src/cloud/monitor.rs b/dozer-cli/src/cloud/monitor.rs index 385feb58f8..36c8be12af 100644 --- a/dozer-cli/src/cloud/monitor.rs +++ b/dozer-cli/src/cloud/monitor.rs @@ -1,6 +1,5 @@ use crate::cli::cloud::Cloud; use crate::cloud::client::get_grpc_cloud_client; -use crate::cloud::cloud_app_context::CloudAppContext; use crate::cloud::token_layer::TokenLayer; use crate::errors::CloudError; use dozer_types::grpc_types::cloud::dozer_cloud_client::DozerCloudClient; @@ -13,14 +12,10 @@ use tokio::runtime::Runtime; pub fn monitor_app( cloud: &Cloud, - cloud_config: &dozer_types::models::cloud::Cloud, + app_id: String, + cloud_config: Option, runtime: Arc, ) -> Result<(), CloudError> { - let app_id = cloud - .app_id - .clone() - .unwrap_or(CloudAppContext::get_app_id(cloud_config)?); - runtime.block_on(async move { let mut client: DozerCloudClient = get_grpc_cloud_client(cloud, cloud_config).await?; diff --git a/dozer-cli/src/errors.rs b/dozer-cli/src/errors.rs index ff97423846..17628e4151 100644 --- a/dozer-cli/src/errors.rs +++ b/dozer-cli/src/errors.rs @@ -283,4 +283,7 @@ pub enum CloudContextError { #[error("App id not found in configuration. You need to run \"deploy\" or \"set-app\" first")] AppIdNotFound, + + #[error("App id already exists. If you want to create a new app, please remove your cloud configuration")] + AppIdAlreadyExists(String), } diff --git a/dozer-cli/src/main.rs b/dozer-cli/src/main.rs index c72eb53515..b287193e6b 100644 --- a/dozer-cli/src/main.rs +++ b/dozer-cli/src/main.rs @@ -108,7 +108,7 @@ async fn check_update() { // We dont show error if error is connection error, because mostly it happens // when main thread is shutting down before request completes. if !e.is_connect() { - warn!("Unable to fetch the latest metadata"); + warn!("Unable to fetch the latest metadata"); } debug!("Updates check error: {}", e); @@ -136,53 +136,42 @@ fn run() -> Result<(), OrchestrationError> { if let Commands::Cloud(cloud) = &cli.cmd { render_logo(); let cloud = cloud.clone(); - let res = if let CloudCommands::Login { - organisation_slug, - profile_name, - client_id, - client_secret, - } = cloud.command.clone() - { - CloudClient::login( - runtime.clone(), + + let config = init_configuration(&cli, runtime.clone()).ok(); + let mut cloud_client = CloudClient::new(config.clone(), runtime.clone()); + let res = match cloud.command.clone() { + CloudCommands::Deploy(deploy) => { + cloud_client.deploy(cloud, deploy, cli.config_paths.clone()) + } + CloudCommands::Login { + organisation_slug, + profile_name, + client_id, + client_secret, + } => cloud_client.login( cloud, organisation_slug, profile_name, client_id, client_secret, - ) - } else { - let config = init_configuration(&cli, runtime.clone())?; - let mut cloud_client = CloudClient::new(config.clone(), runtime.clone()); - match cloud.command.clone() { - CloudCommands::Deploy(deploy) => { - cloud_client.deploy(cloud, deploy, cli.config_paths.clone()) - } - CloudCommands::Login { - organisation_slug: _, - profile_name: _, - client_id: _, - client_secret: _, - } => unreachable!("This is handled earlier"), - CloudCommands::Secrets(command) => { - cloud_client.execute_secrets_command(cloud, command) - } - CloudCommands::Delete => cloud_client.delete(cloud), - CloudCommands::Status => cloud_client.status(cloud), - CloudCommands::Monitor => cloud_client.monitor(cloud), - CloudCommands::Logs(logs) => cloud_client.trace_logs(cloud, logs), - CloudCommands::Version(version) => cloud_client.version(cloud, version), - CloudCommands::List(list) => cloud_client.list(cloud, list), - CloudCommands::SetApp { app_id } => { - CloudAppContext::save_app_id(app_id.clone())?; - info!("Using \"{app_id}\" app"); - Ok(()) - } - CloudCommands::ApiRequestSamples { endpoint } => { - cloud_client.print_api_request_samples(cloud, endpoint) - } + ), + CloudCommands::Secrets(command) => cloud_client.execute_secrets_command(cloud, command), + CloudCommands::Delete => cloud_client.delete(cloud), + CloudCommands::Status => cloud_client.status(cloud), + CloudCommands::Monitor => cloud_client.monitor(cloud), + CloudCommands::Logs(logs) => cloud_client.trace_logs(cloud, logs), + CloudCommands::Version(version) => cloud_client.version(cloud, version), + CloudCommands::List(list) => cloud_client.list(cloud, list), + CloudCommands::SetApp { app_id } => { + CloudAppContext::save_app_id(app_id.clone())?; + info!("Using \"{app_id}\" app"); + Ok(()) + } + CloudCommands::ApiRequestSamples { endpoint } => { + cloud_client.print_api_request_samples(cloud, endpoint) } }; + return dozer_tracing::init_telemetry_closure( None, &Default::default(), @@ -326,10 +315,7 @@ fn init_configuration(cli: &Cli, runtime: Arc) -> Result { - error!("{}", e); - Err(e) - } + Err(e) => Err(e), } }, )