diff --git a/examples/create_simple_infrastructure_on_aws.rs b/examples/create_simple_infrastructure_on_aws.rs index 56e82f56..e7261900 100644 --- a/examples/create_simple_infrastructure_on_aws.rs +++ b/examples/create_simple_infrastructure_on_aws.rs @@ -1,14 +1,14 @@ use chrono::Utc; use qovery_engine::build_platform::local_docker::LocalDocker; -use qovery_engine::cloud_provider::aws::AWS; -use qovery_engine::cloud_provider::aws::kubernetes::{EKS, Options}; use qovery_engine::cloud_provider::aws::kubernetes::node::Node; +use qovery_engine::cloud_provider::aws::kubernetes::{Options, EKS}; +use qovery_engine::cloud_provider::aws::AWS; use qovery_engine::cloud_provider::TerraformStateCredentials; use qovery_engine::container_registry::ecr::ECR; use qovery_engine::dns_provider::cloudflare::Cloudflare; use qovery_engine::engine::Engine; -use qovery_engine::error::ConfigurationError; +use qovery_engine::error::EngineError; use qovery_engine::models::{ Action, Application, Context, Environment, EnvironmentAction, GitCredentials, Kind, }; @@ -82,10 +82,10 @@ fn main() { let session = match engine.session() { Ok(session) => session, Err(config_error) => match config_error { - ConfigurationError::BuildPlatform(_) => panic!("build platform config error"), - ConfigurationError::ContainerRegistry(_) => panic!("container registry config error"), - ConfigurationError::CloudProvider(_) => panic!("cloud provider config error"), - ConfigurationError::DnsProvider(_) => panic!("dns provider config error"), + EngineError::BuildPlatform(_) => panic!("build platform config error"), + EngineError::ContainerRegistry(_) => panic!("container registry config error"), + EngineError::CloudProvider(_) => panic!("cloud provider config error"), + EngineError::DnsProvider(_) => panic!("dns provider config error"), }, }; diff --git a/examples/deploy_app_on_aws.rs b/examples/deploy_app_on_aws.rs index e2ba24f8..76f7990b 100644 --- a/examples/deploy_app_on_aws.rs +++ b/examples/deploy_app_on_aws.rs @@ -1,15 +1,15 @@ use chrono::Utc; use qovery_engine::build_platform::local_docker::LocalDocker; -use qovery_engine::cloud_provider::aws::AWS; -use qovery_engine::cloud_provider::aws::kubernetes::{EKS, Options}; use qovery_engine::cloud_provider::aws::kubernetes::node::Node; +use qovery_engine::cloud_provider::aws::kubernetes::{Options, EKS}; use qovery_engine::cloud_provider::aws::router::Router; +use qovery_engine::cloud_provider::aws::AWS; use qovery_engine::cloud_provider::TerraformStateCredentials; use qovery_engine::container_registry::ecr::ECR; use qovery_engine::dns_provider::cloudflare::Cloudflare; use qovery_engine::engine::Engine; -use qovery_engine::error::ConfigurationError; +use qovery_engine::error::EngineError; use qovery_engine::models::{ Action, Application, Context, Environment, EnvironmentAction, GitCredentials, Kind, }; @@ -83,10 +83,10 @@ fn main() { let session = match engine.session() { Ok(session) => session, Err(config_error) => match config_error { - ConfigurationError::BuildPlatform(_) => panic!("build platform config error"), - ConfigurationError::ContainerRegistry(_) => panic!("container registry config error"), - ConfigurationError::CloudProvider(_) => panic!("cloud provider config error"), - ConfigurationError::DnsProvider(_) => panic!("dns provider config error"), + EngineError::BuildPlatform(_) => panic!("build platform config error"), + EngineError::ContainerRegistry(_) => panic!("container registry config error"), + EngineError::CloudProvider(_) => panic!("cloud provider config error"), + EngineError::DnsProvider(_) => panic!("dns provider config error"), }, }; diff --git a/src/build_platform/error.rs b/src/build_platform/error.rs deleted file mode 100644 index 61f366e8..00000000 --- a/src/build_platform/error.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[derive(Debug)] -pub enum BuildPlatformError { - Unexpected(String), -} diff --git a/src/build_platform/local_docker.rs b/src/build_platform/local_docker.rs index e253b73b..f9b864ac 100644 --- a/src/build_platform/local_docker.rs +++ b/src/build_platform/local_docker.rs @@ -1,15 +1,16 @@ use std::path::Path; use std::rc::Rc; -use crate::build_platform::error::BuildPlatformError; -use crate::build_platform::{Build, BuildError, BuildPlatform, BuildResult, Image, Kind}; +use git2::Error; + +use crate::build_platform::{Build, BuildPlatform, BuildResult, Image, Kind}; +use crate::error::{EngineError, EngineErrorCause, EngineErrorScope}; use crate::fs::workspace_directory; use crate::git::checkout_submodules; use crate::models::{ Context, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressListener, ProgressScope, }; -use crate::transaction::CommitError::BuildImage; use crate::{cmd, git}; /// use Docker in local @@ -30,7 +31,7 @@ impl LocalDocker { } } - fn image_does_exist(&self, image: &Image) -> Result { + fn image_does_exist(&self, image: &Image) -> Result { let envs = match self.context.docker_tcp_socket() { Some(tcp_socket) => vec![("DOCKER_HOST", tcp_socket.as_str())], None => vec![], @@ -66,10 +67,11 @@ impl BuildPlatform for LocalDocker { self.name.as_str() } - fn is_valid(&self) -> Result<(), BuildPlatformError> { + fn is_valid(&self) -> Result<(), EngineError> { if !crate::cmd::utilities::does_binary_exist("docker") { - return Err(BuildPlatformError::Unexpected( - "docker binary not found".to_string(), + return Err(self.engine_error( + EngineErrorCause::Internal, + String::from("docker binary not found"), )); } @@ -80,7 +82,7 @@ impl BuildPlatform for LocalDocker { self.listeners.push(listener); } - fn build(&self, build: Build, force_build: bool) -> Result { + fn build(&self, build: Build, force_build: bool) -> Result { info!("LocalDocker.build() called for {}", self.name()); let listeners_helper = ListenersHelper::new(&self.listeners); @@ -111,8 +113,14 @@ impl BuildPlatform for LocalDocker { match git_clone { Ok(_) => {} Err(err) => { - error! {"Error while trying to clone repository {}", build.git_repository.url} - return Err(BuildError::Git(err)); + let message = format!( + "Error while cloning repository {}. Error: {:?}", + &build.git_repository.url, err + ); + + error!("{}", message); + + return Err(self.engine_error(EngineErrorCause::Internal, message)); } } @@ -121,12 +129,32 @@ impl BuildPlatform for LocalDocker { let commit_id = &build.git_repository.commit_id; match git::checkout(&repo, &commit_id, build.git_repository.url.as_str()) { Ok(_) => {} - Err(err) => return Err(BuildError::Git(err)), + Err(err) => { + let message = format!( + "Error while git checkout repository {} with commit id {}. Error: {:?}", + &build.git_repository.url, commit_id, err + ); + + error!("{}", message); + + return Err(self.engine_error(EngineErrorCause::Internal, message)); + } } // git checkout submodules - let _ = checkout_submodules(&repo); - // TODO what if we can't checkout submodules? Today we ignore it + match checkout_submodules(repo) { + Ok(_) => {} + Err(err) => { + let message = format!( + "Error while checkout submodules from repository {}. Error: {:?}", + &build.git_repository.url, err + ); + + error!("{}", message); + + return Err(self.engine_error(EngineErrorCause::Internal, message)); + } + } let into_dir_docker_style = format!("{}/.", into_dir.as_str()); @@ -140,11 +168,14 @@ impl BuildPlatform for LocalDocker { match Path::new(dockerfile_complete_path.as_str()).exists() { false => { - error!( + let message = format!( "Unable to find Dockerfile path {}", dockerfile_complete_path.as_str() ); - return Err(BuildError::Error); + + error!("{}", &message); + + return Err(self.engine_error(EngineErrorCause::Internal, message)); } _ => {} } @@ -220,7 +251,19 @@ impl BuildPlatform for LocalDocker { match exit_status { Ok(_) => {} - Err(_) => return Err(BuildError::Error), + Err(err) => { + return Err(self.engine_error( + EngineErrorCause::User( + "It looks like your Dockerfile is wrong. Did you consider building \ + your container locally using `qovery run` or `docker build`?", + ), + format!( + "error while building container image {}. Error: {:?}", + self.name_with_id(), + err + ), + )); + } } listeners_helper.start_in_progress(ProgressInfo::new( @@ -228,27 +271,34 @@ impl BuildPlatform for LocalDocker { id: build.image.application_id.clone(), }, ProgressLevel::Info, - Some("build is done ✔"), + Some(format!( + "container build is done for {} ✔", + self.name_with_id() + )), self.context.execution_id(), )); Ok(BuildResult { build }) } - fn build_error(&self, build: Build) -> Result { + fn build_error(&self, build: Build) -> Result { warn!("LocalDocker.build_error() called for {}", self.name()); let listener_helper = ListenersHelper::new(&self.listeners); + + // FIXME + let message = String::from("something goes wrong (not implemented)"); + listener_helper.error(ProgressInfo::new( ProgressScope::Application { id: build.image.application_id, }, ProgressLevel::Error, - Some("something goes wrong (not implemented)"), + Some(message.as_str()), self.context.execution_id(), )); // FIXME - Err(BuildError::Error) + Err(self.engine_error(EngineErrorCause::Internal, message)) } } diff --git a/src/build_platform/mod.rs b/src/build_platform/mod.rs index 8686fb99..b0923d56 100644 --- a/src/build_platform/mod.rs +++ b/src/build_platform/mod.rs @@ -3,11 +3,10 @@ use std::rc::Rc; use git2::Error; use serde::{Deserialize, Serialize}; -use crate::build_platform::error::BuildPlatformError; +use crate::error::{EngineError, EngineErrorCause, EngineErrorScope}; use crate::git::Credentials; use crate::models::{Context, ProgressListener}; -pub mod error; pub mod local_docker; pub trait BuildPlatform { @@ -15,10 +14,24 @@ pub trait BuildPlatform { fn kind(&self) -> Kind; fn id(&self) -> &str; fn name(&self) -> &str; - fn is_valid(&self) -> Result<(), BuildPlatformError>; + fn name_with_id(&self) -> String { + format!("{} ({})", self.name(), self.id()) + } + fn is_valid(&self) -> Result<(), EngineError>; fn add_listener(&mut self, listener: Rc>); - fn build(&self, build: Build, force_build: bool) -> Result; - fn build_error(&self, build: Build) -> Result; + fn build(&self, build: Build, force_build: bool) -> Result; + fn build_error(&self, build: Build) -> Result; + fn engine_error_scope(&self) -> EngineErrorScope { + EngineErrorScope::BuildPlatform(self.id().to_string(), self.name().to_string()) + } + fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError { + EngineError::new( + cause, + self.engine_error_scope(), + self.context().execution_id(), + Some(message), + ) + } } pub struct Build { @@ -62,12 +75,6 @@ pub struct BuildResult { pub build: Build, } -#[derive(Debug)] -pub enum BuildError { - Git(Error), - Error, -} - #[derive(Serialize, Deserialize, Clone)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum Kind { diff --git a/src/cloud_provider/aws/application.rs b/src/cloud_provider/aws/application.rs index 71c99cef..0590466e 100644 --- a/src/cloud_provider/aws/application.rs +++ b/src/cloud_provider/aws/application.rs @@ -6,11 +6,14 @@ use crate::cloud_provider::aws::{common, AWS}; use crate::cloud_provider::environment::Environment; use crate::cloud_provider::kubernetes::Kubernetes; use crate::cloud_provider::service::{ - Action, Application as CApplication, Create, Delete, Pause, Service, ServiceError, ServiceType, + Action, Application as CApplication, Create, Delete, Pause, Service, ServiceType, StatelessService, }; use crate::cloud_provider::DeploymentTarget; use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY}; +use crate::error::{ + from_simple_error_to_engine_error, EngineError, EngineErrorCause, EngineErrorScope, +}; use crate::models::Context; #[derive(Clone, Eq, PartialEq, Hash)] @@ -126,7 +129,7 @@ impl Application { context } - fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), ServiceError> { + fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), EngineError> { let (kubernetes, environment) = match target { DeploymentTarget::ManagedServices(k, env) => (*k, *env), DeploymentTarget::SelfHosted(k, env) => (*k, *env), @@ -137,20 +140,28 @@ impl Application { let selector = format!("app={}", self.name()); if is_error { - let _ = common::get_stateless_resource_information( - kubernetes, - environment, - workspace_dir.as_str(), - selector.as_str(), + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + common::get_stateless_resource_information( + kubernetes, + environment, + workspace_dir.as_str(), + selector.as_str(), + ), )?; } // clean the resource - let _ = common::do_stateless_service_cleanup( - kubernetes, - environment, - workspace_dir.as_str(), - helm_release_name.as_str(), + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + common::do_stateless_service_cleanup( + kubernetes, + environment, + workspace_dir.as_str(), + helm_release_name.as_str(), + ), )?; Ok(()) @@ -212,7 +223,7 @@ impl Service for Application { } impl Create for Application { - fn on_create(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { info!("AWS.application.on_create() called for {}", self.name()); let (kubernetes, environment) = match target { DeploymentTarget::ManagedServices(k, env) => (*k, *env), @@ -229,10 +240,15 @@ impl Create for Application { let workspace_dir = self.workspace_directory(); let from_dir = format!("{}/aws/charts/q-application", self.context.lib_root_dir()); - let _ = crate::template::generate_and_copy_all_files_into_dir( - from_dir.as_str(), - workspace_dir.as_str(), - &context, + + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + from_dir.as_str(), + workspace_dir.as_str(), + &context, + ), )?; // render @@ -243,51 +259,67 @@ impl Create for Application { (AWS_SECRET_ACCESS_KEY, aws.secret_access_key.as_str()), ]; - let kubernetes_config_file_path = common::kubernetes_config_path( - workspace_dir.as_str(), - environment.organization_id.as_str(), - kubernetes.id(), - aws.access_key_id.as_str(), - aws.secret_access_key.as_str(), - kubernetes.region(), + let kubernetes_config_file_path = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + common::kubernetes_config_path( + workspace_dir.as_str(), + environment.organization_id.as_str(), + kubernetes.id(), + aws.access_key_id.as_str(), + aws.secret_access_key.as_str(), + kubernetes.region(), + ), )?; // do exec helm upgrade and return the last deployment status - let helm_history_row = crate::cmd::helm::helm_exec_with_upgrade_history( - kubernetes_config_file_path.as_str(), - environment.namespace(), - helm_release_name.as_str(), - workspace_dir.as_str(), - aws_credentials_envs.clone(), + let helm_history_row = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::cmd::helm::helm_exec_with_upgrade_history( + kubernetes_config_file_path.as_str(), + environment.namespace(), + helm_release_name.as_str(), + workspace_dir.as_str(), + aws_credentials_envs.clone(), + ), )?; // check deployment status if helm_history_row.is_none() || !helm_history_row.unwrap().is_successfully_deployed() { - // TODO get pod output by using kubectl and return it into the OnCreateFailed - return Err(ServiceError::OnCreateFailed); + return Err(self.engine_error( + EngineErrorCause::User( + "Your application didn't start for some reason. \ + Are you sure your application is correctly running? You can give a try by running \ + locally `qovery run`. You can also check the application log from the web \ + interface or the CLI with `qovery log`", + ), + format!("Application {} has failed to start ⤬", self.name_with_id()), + )); } // check app status let selector = format!("app={}", self.name()); - match crate::cmd::kubectl::kubectl_exec_is_pod_ready_with_retry( - kubernetes_config_file_path.as_str(), - environment.namespace(), - selector.as_str(), - aws_credentials_envs, - ) { - Ok(Some(true)) => {} - _ => return Err(ServiceError::OnCreateFailed), - } + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::cmd::kubectl::kubectl_exec_is_pod_ready_with_retry( + kubernetes_config_file_path.as_str(), + environment.namespace(), + selector.as_str(), + aws_credentials_envs, + ), + )?; Ok(()) } - fn on_create_check(&self) -> Result<(), ServiceError> { + fn on_create_check(&self) -> Result<(), EngineError> { Ok(()) } - fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { warn!( "AWS.application.on_create_error() called for {}", self.name() @@ -311,29 +343,42 @@ impl Create for Application { (AWS_SECRET_ACCESS_KEY, aws.secret_access_key.as_str()), ]; - let kubernetes_config_file_path = common::kubernetes_config_path( - workspace_dir.as_str(), - environment.organization_id.as_str(), - kubernetes.id(), - aws.access_key_id.as_str(), - aws.secret_access_key.as_str(), - kubernetes.region(), + let kubernetes_config_file_path = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + common::kubernetes_config_path( + workspace_dir.as_str(), + environment.organization_id.as_str(), + kubernetes.id(), + aws.access_key_id.as_str(), + aws.secret_access_key.as_str(), + kubernetes.region(), + ), )?; let helm_release_name = self.helm_release_name(); - let history_rows = crate::cmd::helm::helm_exec_history( - kubernetes_config_file_path.as_str(), - environment.namespace(), - helm_release_name.as_str(), - aws_credentials_envs.clone(), - )?; - if history_rows.len() == 1 { - crate::cmd::helm::helm_exec_uninstall( + let history_rows = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::cmd::helm::helm_exec_history( kubernetes_config_file_path.as_str(), environment.namespace(), helm_release_name.as_str(), aws_credentials_envs.clone(), + ), + )?; + + if history_rows.len() == 1 { + from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::cmd::helm::helm_exec_uninstall( + kubernetes_config_file_path.as_str(), + environment.namespace(), + helm_release_name.as_str(), + aws_credentials_envs.clone(), + ), )?; } Ok(()) @@ -341,16 +386,16 @@ impl Create for Application { } impl Pause for Application { - fn on_pause(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { info!("AWS.application.on_pause() called for {}", self.name()); self.delete(target, false) } - fn on_pause_check(&self) -> Result<(), ServiceError> { + fn on_pause_check(&self) -> Result<(), EngineError> { Ok(()) } - fn on_pause_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_pause_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { warn!( "AWS.application.on_pause_error() called for {}", self.name() @@ -360,16 +405,16 @@ impl Pause for Application { } impl Delete for Application { - fn on_delete(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { info!("AWS.application.on_delete() called for {}", self.name()); self.delete(target, false) } - fn on_delete_check(&self) -> Result<(), ServiceError> { + fn on_delete_check(&self) -> Result<(), EngineError> { Ok(()) } - fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { warn!( "AWS.application.on_delete_error() called for {}", self.name() diff --git a/src/cloud_provider/aws/common.rs b/src/cloud_provider/aws/common.rs index 59fc6a93..b2c2c348 100644 --- a/src/cloud_provider/aws/common.rs +++ b/src/cloud_provider/aws/common.rs @@ -6,9 +6,11 @@ use rusoto_core::Region; use crate::cloud_provider::aws::AWS; use crate::cloud_provider::environment::Environment; use crate::cloud_provider::kubernetes::Kubernetes; -use crate::cloud_provider::service::ServiceError; -use crate::cmd::utilities::CmdError; use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY}; +use crate::error::{EngineError, SimpleError}; + +pub type Logs = String; +pub type Describe = String; pub fn kubernetes_config_path( workspace_directory: &str, @@ -17,7 +19,7 @@ pub fn kubernetes_config_path( access_key_id: &str, secret_access_key: &str, region: &str, -) -> Result { +) -> Result { let kubernetes_config_bucket_name = format!("qovery-kubeconfigs-{}", kubernetes_cluster_id); let kubernetes_config_object_key = format!("{}.yaml", kubernetes_cluster_id); @@ -40,16 +42,13 @@ pub fn kubernetes_config_path( Ok(kubernetes_config_file_path) } -pub type Logs = String; -pub type Describe = String; - /// show different output (kubectl describe, log..) for debug purpose pub fn get_stateless_resource_information( kubernetes: &dyn Kubernetes, environment: &Environment, workspace_dir: &str, selector: &str, -) -> Result<(Describe, Logs), CmdError> { +) -> Result<(Describe, Logs), SimpleError> { let aws = kubernetes .cloud_provider() .as_any() @@ -112,7 +111,7 @@ pub fn do_stateless_service_cleanup( environment: &Environment, workspace_dir: &str, helm_release_name: &str, -) -> Result<(), ServiceError> { +) -> Result<(), SimpleError> { let aws = kubernetes .cloud_provider() .as_any() diff --git a/src/cloud_provider/aws/databases/mongodb.rs b/src/cloud_provider/aws/databases/mongodb.rs index 5512e71e..7ac67119 100644 --- a/src/cloud_provider/aws/databases/mongodb.rs +++ b/src/cloud_provider/aws/databases/mongodb.rs @@ -6,10 +6,13 @@ use crate::cloud_provider::environment::Environment; use crate::cloud_provider::kubernetes::Kubernetes; use crate::cloud_provider::service::{ Action, Backup, Create, Database, DatabaseOptions, DatabaseType, Delete, Downgrade, Pause, - Service, ServiceError, ServiceType, StatefulService, Upgrade, + Service, ServiceType, StatefulService, Upgrade, }; use crate::cloud_provider::DeploymentTarget; use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY}; +use crate::error::{ + from_simple_error_to_engine_error, EngineError, EngineErrorCause, EngineErrorScope, +}; use crate::models::Context; pub struct MongoDB { @@ -79,12 +82,14 @@ impl MongoDB { .as_any() .downcast_ref::() .expect("Could not downcast kubernetes.cloud_provider() to AWS"); + // we need the kubernetes config file to store tfstates file in kube secrets let kubernetes_config_file_path = utilities::get_kubernetes_config_path( self.workspace_directory().as_str(), kubernetes, environment, ); + match kubernetes_config_file_path { Ok(kube_config) => { context.insert("kubeconfig_path", &kube_config.as_str()); @@ -96,8 +101,12 @@ impl MongoDB { utilities::create_namespace(&environment.namespace(), kube_config.as_str(), aws); } - Err(e) => error!("Failed to generate the kubernetes config file path: {}", e), + Err(e) => error!( + "Failed to generate the kubernetes config file path: {:?}", + e + ), } + context.insert("namespace", environment.namespace()); context.insert("aws_access_key", &cp.access_key_id); @@ -122,7 +131,7 @@ impl MongoDB { context } - fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), ServiceError> { + fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), EngineError> { let workspace_dir = self.workspace_directory(); match target { @@ -134,39 +143,58 @@ impl MongoDB { let context = self.tera_context(*kubernetes, *environment); - crate::template::generate_and_copy_all_files_into_dir( - format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(), - &workspace_dir, - &context, + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(), + &workspace_dir, + &context, + ), )?; - crate::template::generate_and_copy_all_files_into_dir( - format!("{}/aws/services/mongodb", self.context.lib_root_dir()).as_str(), - workspace_dir.as_str(), - &context, + + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!("{}/aws/services/mongodb", self.context.lib_root_dir()).as_str(), + workspace_dir.as_str(), + &context, + ), )?; - crate::template::generate_and_copy_all_files_into_dir( - format!( - "{}/aws/charts/external-name-svc", - self.context.lib_root_dir() - ) - .as_str(), - format!("{}/{}", workspace_dir, "external-name-svc").as_str(), - &context, + + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!( + "{}/aws/charts/external-name-svc", + self.context.lib_root_dir() + ) + .as_str(), + format!("{}/{}", workspace_dir, "external-name-svc").as_str(), + &context, + ), )?; - crate::template::generate_and_copy_all_files_into_dir( - format!( - "{}/aws/charts/external-name-svc", - self.context.lib_root_dir() - ) - .as_str(), - workspace_dir.as_str(), - &context, + + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!( + "{}/aws/charts/external-name-svc", + self.context.lib_root_dir() + ) + .as_str(), + workspace_dir.as_str(), + &context, + ), )?; match crate::cmd::terraform::terraform_exec_with_init_validate_destroy( workspace_dir.as_str(), ) { - Ok(o) => { + Ok(_) => { info!("Deleting secrets containing tfstates"); utilities::delete_terraform_tfstate_secret( *kubernetes, @@ -174,8 +202,16 @@ impl MongoDB { self.workspace_directory().as_str(), ); } - //TODO: find a way to raise the error - Err(e) => error!("Error while destroying infrastructure {}", e), + Err(e) => { + let message = format!( + "Error while destroying infrastructure {}", + e.message.unwrap_or("".into()) + ); + + error!("{}", message); + + return Err(self.engine_error(EngineErrorCause::Internal, message)); + } } } DeploymentTarget::SelfHosted(kubernetes, environment) => { @@ -183,20 +219,28 @@ impl MongoDB { let selector = format!("app={}", self.name()); if is_error { - let _ = common::get_stateless_resource_information( - *kubernetes, - *environment, - workspace_dir.as_str(), - selector.as_str(), + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + common::get_stateless_resource_information( + *kubernetes, + *environment, + workspace_dir.as_str(), + selector.as_str(), + ), )?; } // clean the resource - let _ = common::do_stateless_service_cleanup( - *kubernetes, - *environment, - workspace_dir.as_str(), - helm_release_name.as_str(), + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + common::do_stateless_service_cleanup( + *kubernetes, + *environment, + workspace_dir.as_str(), + helm_release_name.as_str(), + ), )?; } } @@ -252,7 +296,7 @@ impl Service for MongoDB { impl Database for MongoDB {} impl Create for MongoDB { - fn on_create(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { info!("AWS.MongoDB.on_create() called for {}", self.name()); let workspace_dir = self.workspace_directory(); @@ -264,30 +308,48 @@ impl Create for MongoDB { let context = self.tera_context(*kubernetes, *environment); let workspace_dir = self.workspace_directory(); - crate::template::generate_and_copy_all_files_into_dir( - format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(), - &workspace_dir, - &context, + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(), + &workspace_dir, + &context, + ), )?; - crate::template::generate_and_copy_all_files_into_dir( - format!("{}/aws/services/mongodb", self.context.lib_root_dir()).as_str(), - workspace_dir.as_str(), - &context, + + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!("{}/aws/services/mongodb", self.context.lib_root_dir()).as_str(), + workspace_dir.as_str(), + &context, + ), )?; - crate::template::generate_and_copy_all_files_into_dir( - format!( - "{}/aws/charts/external-name-svc", - self.context.lib_root_dir() - ) - .as_str(), - format!("{}/{}", workspace_dir, "external-name-svc").as_str(), - &context, + + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!( + "{}/aws/charts/external-name-svc", + self.context.lib_root_dir() + ) + .as_str(), + format!("{}/{}", workspace_dir, "external-name-svc").as_str(), + &context, + ), )?; // deploy database + external DNS - crate::cmd::terraform::terraform_exec_with_init_validate_plan_apply( - workspace_dir.as_str(), - false, + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::cmd::terraform::terraform_exec_with_init_validate_plan_apply( + workspace_dir.as_str(), + false, + ), )?; } DeploymentTarget::SelfHosted(kubernetes, environment) => { @@ -301,21 +363,29 @@ impl Create for MongoDB { .downcast_ref::() .unwrap(); - let kubernetes_config_file_path = common::kubernetes_config_path( - workspace_dir.as_str(), - environment.organization_id.as_str(), - kubernetes.id(), - aws.access_key_id.as_str(), - aws.secret_access_key.as_str(), - kubernetes.region(), + let kubernetes_config_file_path = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + common::kubernetes_config_path( + workspace_dir.as_str(), + environment.organization_id.as_str(), + kubernetes.id(), + aws.access_key_id.as_str(), + aws.secret_access_key.as_str(), + kubernetes.region(), + ), )?; let from_dir = format!("{}/common/services/mongodb", self.context.lib_root_dir()); - let _ = crate::template::generate_and_copy_all_files_into_dir( - from_dir.as_str(), - workspace_dir.as_str(), - &context, + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + from_dir.as_str(), + workspace_dir.as_str(), + &context, + ), )?; // render templates @@ -326,19 +396,26 @@ impl Create for MongoDB { ]; // do exec helm upgrade and return the last deployment status - let helm_history_row = crate::cmd::helm::helm_exec_with_upgrade_history( - kubernetes_config_file_path.as_str(), - environment.namespace(), - helm_release_name.as_str(), - workspace_dir.as_str(), - aws_credentials_envs.clone(), + let helm_history_row = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::cmd::helm::helm_exec_with_upgrade_history( + kubernetes_config_file_path.as_str(), + environment.namespace(), + helm_release_name.as_str(), + workspace_dir.as_str(), + aws_credentials_envs.clone(), + ), )?; // check deployment status if helm_history_row.is_none() || !helm_history_row.unwrap().is_successfully_deployed() { - return Err(ServiceError::OnCreateFailed); + return Err(self.engine_error( + EngineErrorCause::Internal, + "MongoDB database fails to be deployed (before start)".into(), + )); } // check app status @@ -351,7 +428,16 @@ impl Create for MongoDB { aws_credentials_envs, ) { Ok(Some(true)) => {} - _ => return Err(ServiceError::OnCreateFailed), + _ => { + return Err(self.engine_error( + EngineErrorCause::Internal, + format!( + "MongoDB database {} with id {} failed to start after several retries", + self.name(), + self.id() + ), + )); + } } } } @@ -359,11 +445,11 @@ impl Create for MongoDB { Ok(()) } - fn on_create_check(&self) -> Result<(), ServiceError> { + fn on_create_check(&self) -> Result<(), EngineError> { Ok(()) } - fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { warn!("AWS.MongoDB.on_create_error() called for {}", self.name()); self.delete(target, true) @@ -371,7 +457,7 @@ impl Create for MongoDB { } impl Pause for MongoDB { - fn on_pause(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_pause(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { info!("AWS.MongoDB.on_pause() called for {}", self.name()); // TODO how to pause production? - the goal is to reduce cost, but it is possible to pause a production env? @@ -380,11 +466,11 @@ impl Pause for MongoDB { Ok(()) } - fn on_pause_check(&self) -> Result<(), ServiceError> { + fn on_pause_check(&self) -> Result<(), EngineError> { Ok(()) } - fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { warn!("AWS.MongoDB.on_pause_error() called for {}", self.name()); // TODO what to do if there is a pause error? @@ -394,85 +480,85 @@ impl Pause for MongoDB { } impl Delete for MongoDB { - fn on_delete(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { info!("AWS.MongoDB.on_delete() called for {}", self.name()); self.delete(target, false) } - fn on_delete_check(&self) -> Result<(), ServiceError> { + fn on_delete_check(&self) -> Result<(), EngineError> { Ok(()) } - fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { warn!("AWS.MongoDB.on_create_error() called for {}", self.name()); self.delete(target, true) } } impl crate::cloud_provider::service::Clone for MongoDB { - fn on_clone(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_clone(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } - fn on_clone_check(&self) -> Result<(), ServiceError> { + fn on_clone_check(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_clone_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_clone_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } } impl Upgrade for MongoDB { - fn on_upgrade(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_upgrade(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } - fn on_upgrade_check(&self) -> Result<(), ServiceError> { + fn on_upgrade_check(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_upgrade_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_upgrade_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } } impl Downgrade for MongoDB { - fn on_downgrade(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_downgrade(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } - fn on_downgrade_check(&self) -> Result<(), ServiceError> { + fn on_downgrade_check(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_downgrade_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_downgrade_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } } impl Backup for MongoDB { - fn on_backup(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_backup(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } - fn on_backup_check(&self) -> Result<(), ServiceError> { + fn on_backup_check(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_backup_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_backup_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } - fn on_restore(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_restore(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } - fn on_restore_check(&self) -> Result<(), ServiceError> { + fn on_restore_check(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_restore_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_restore_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } } diff --git a/src/cloud_provider/aws/databases/mysql.rs b/src/cloud_provider/aws/databases/mysql.rs index 53674604..281e3c32 100644 --- a/src/cloud_provider/aws/databases/mysql.rs +++ b/src/cloud_provider/aws/databases/mysql.rs @@ -6,14 +6,17 @@ use crate::cloud_provider::aws::{common, AWS}; use crate::cloud_provider::environment::Environment; use crate::cloud_provider::kubernetes::Kubernetes; use crate::cloud_provider::service::{ - Action, Backup, Create, DatabaseOptions, DatabaseType, Delete, Downgrade, Pause, Service, - ServiceError, ServiceType, StatefulService, Upgrade, + Action, Backup, Create, Database, DatabaseOptions, DatabaseType, Delete, Downgrade, Pause, + Service, ServiceType, StatefulService, Upgrade, }; use crate::cloud_provider::DeploymentTarget; use crate::cmd::kubectl::{ kubectl_exec_create_namespace, kubectl_exec_delete_namespace, kubectl_exec_delete_secret, }; use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY}; +use crate::error::{ + from_simple_error_to_engine_error, EngineError, EngineErrorCause, EngineErrorScope, +}; use crate::models::Context; pub struct MySQL { @@ -58,9 +61,11 @@ impl MySQL { options, } } + fn helm_release_name(&self) -> String { crate::string::cut(format!("mysql-{}", self.id()), 50) } + fn workspace_directory(&self) -> String { crate::fs::workspace_directory( self.context.workspace_root_dir(), @@ -68,20 +73,24 @@ impl MySQL { format!("databases/{}", self.name()), ) } + fn tera_context(&self, kubernetes: &dyn Kubernetes, environment: &Environment) -> TeraContext { let mut context = self.default_tera_context(kubernetes, environment); + // FIXME: is there an other way than downcast a pointer? let cp = kubernetes .cloud_provider() .as_any() .downcast_ref::() .expect("Could not downcast kubernetes.cloud_provider() to AWS"); + // we need the kubernetes config file to store tfstates file in kube secrets let kubernetes_config_file_path = utilities::get_kubernetes_config_path( self.workspace_directory().as_str(), kubernetes, environment, ); + match kubernetes_config_file_path { Ok(kube_config) => { context.insert("kubeconfig_path", &kube_config.as_str()); @@ -93,8 +102,12 @@ impl MySQL { utilities::create_namespace(&environment.namespace(), kube_config.as_str(), aws); } - Err(e) => error!("Failed to generate the kubernetes config file path: {}", e), + Err(e) => error!( + "Failed to generate the kubernetes config file path: {:?}", + e + ), } + context.insert("namespace", environment.namespace()); context.insert("aws_access_key", &cp.access_key_id); @@ -119,7 +132,7 @@ impl MySQL { context } - fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), ServiceError> { + fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), EngineError> { let workspace_dir = self.workspace_directory(); match target { @@ -131,39 +144,58 @@ impl MySQL { let context = self.tera_context(*kubernetes, *environment); - crate::template::generate_and_copy_all_files_into_dir( - format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(), - &workspace_dir, - &context, + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(), + &workspace_dir, + &context, + ), )?; - crate::template::generate_and_copy_all_files_into_dir( - format!("{}/aws/services/mysql", self.context.lib_root_dir()).as_str(), - workspace_dir.as_str(), - &context, + + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!("{}/aws/services/mysql", self.context.lib_root_dir()).as_str(), + workspace_dir.as_str(), + &context, + ), )?; - crate::template::generate_and_copy_all_files_into_dir( - format!( - "{}/aws/charts/external-name-svc", - self.context.lib_root_dir() - ) - .as_str(), - format!("{}/{}", workspace_dir, "external-name-svc").as_str(), - &context, + + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!( + "{}/aws/charts/external-name-svc", + self.context.lib_root_dir() + ) + .as_str(), + format!("{}/{}", workspace_dir, "external-name-svc").as_str(), + &context, + ), )?; - crate::template::generate_and_copy_all_files_into_dir( - format!( - "{}/aws/charts/external-name-svc", - self.context.lib_root_dir() - ) - .as_str(), - workspace_dir.as_str(), - &context, + + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!( + "{}/aws/charts/external-name-svc", + self.context.lib_root_dir() + ) + .as_str(), + workspace_dir.as_str(), + &context, + ), )?; match crate::cmd::terraform::terraform_exec_with_init_validate_destroy( workspace_dir.as_str(), ) { - Ok(o) => { + Ok(_) => { info!("Deleting secrets containing tfstates"); utilities::delete_terraform_tfstate_secret( *kubernetes, @@ -171,8 +203,16 @@ impl MySQL { self.workspace_directory().as_str(), ); } - //TODO: find a way to raise the error - Err(e) => error!("Error while destroying infrastructure {}", e), + Err(e) => { + let message = format!( + "Error while destroying infrastructure {}", + e.message.unwrap_or("".into()) + ); + + error!("{}", message); + + return Err(self.engine_error(EngineErrorCause::Internal, message)); + } } } DeploymentTarget::SelfHosted(kubernetes, environment) => { @@ -180,20 +220,28 @@ impl MySQL { let selector = format!("app={}", self.name()); if is_error { - let _ = common::get_stateless_resource_information( - *kubernetes, - *environment, - workspace_dir.as_str(), - selector.as_str(), + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + common::get_stateless_resource_information( + *kubernetes, + *environment, + workspace_dir.as_str(), + selector.as_str(), + ), )?; } // clean the resource - let _ = common::do_stateless_service_cleanup( - *kubernetes, - *environment, - workspace_dir.as_str(), - helm_release_name.as_str(), + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + common::do_stateless_service_cleanup( + *kubernetes, + *environment, + workspace_dir.as_str(), + helm_release_name.as_str(), + ), )?; } } @@ -246,8 +294,10 @@ impl Service for MySQL { } } +impl Database for MySQL {} + impl Create for MySQL { - fn on_create(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { match target { DeploymentTarget::ManagedServices(kubernetes, environment) => { // use terraform @@ -256,29 +306,47 @@ impl Create for MySQL { let workspace_dir = self.workspace_directory(); - crate::template::generate_and_copy_all_files_into_dir( - format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(), - &workspace_dir, - &context, - )?; - crate::template::generate_and_copy_all_files_into_dir( - format!("{}/aws/services/mysql", self.context.lib_root_dir()).as_str(), - workspace_dir.as_str(), - &context, - )?; - crate::template::generate_and_copy_all_files_into_dir( - format!( - "{}/aws/charts/external-name-svc", - self.context.lib_root_dir() - ) - .as_str(), - format!("{}/{}", workspace_dir, "external-name-svc").as_str(), - &context, + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(), + &workspace_dir, + &context, + ), )?; - crate::cmd::terraform::terraform_exec_with_init_validate_plan_apply( - workspace_dir.as_str(), - false, + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!("{}/aws/services/mysql", self.context.lib_root_dir()).as_str(), + workspace_dir.as_str(), + &context, + ), + )?; + + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!( + "{}/aws/charts/external-name-svc", + self.context.lib_root_dir() + ) + .as_str(), + format!("{}/{}", workspace_dir, "external-name-svc").as_str(), + &context, + ), + )?; + + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::cmd::terraform::terraform_exec_with_init_validate_plan_apply( + workspace_dir.as_str(), + false, + ), )?; } DeploymentTarget::SelfHosted(kubernetes, environment) => { @@ -294,21 +362,29 @@ impl Create for MySQL { .downcast_ref::() .unwrap(); - let kubernetes_config_file_path = common::kubernetes_config_path( - workspace_dir.as_str(), - environment.organization_id.as_str(), - kubernetes.id(), - aws.access_key_id.as_str(), - aws.secret_access_key.as_str(), - kubernetes.region(), + let kubernetes_config_file_path = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + common::kubernetes_config_path( + workspace_dir.as_str(), + environment.organization_id.as_str(), + kubernetes.id(), + aws.access_key_id.as_str(), + aws.secret_access_key.as_str(), + kubernetes.region(), + ), )?; let from_dir = format!("{}/common/services/mysql", self.context.lib_root_dir()); - let _ = crate::template::generate_and_copy_all_files_into_dir( - from_dir.as_str(), - workspace_dir.as_str(), - &context, + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + from_dir.as_str(), + workspace_dir.as_str(), + &context, + ), )?; // render templates @@ -319,19 +395,26 @@ impl Create for MySQL { ]; // do exec helm upgrade and return the last deployment status - let helm_history_row = crate::cmd::helm::helm_exec_with_upgrade_history( - kubernetes_config_file_path.as_str(), - environment.namespace(), - helm_release_name.as_str(), - workspace_dir.as_str(), - aws_credentials_envs.clone(), + let helm_history_row = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::cmd::helm::helm_exec_with_upgrade_history( + kubernetes_config_file_path.as_str(), + environment.namespace(), + helm_release_name.as_str(), + workspace_dir.as_str(), + aws_credentials_envs.clone(), + ), )?; // check deployment status if helm_history_row.is_none() || !helm_history_row.unwrap().is_successfully_deployed() { - return Err(ServiceError::OnCreateFailed); + return Err(self.engine_error( + EngineErrorCause::Internal, + "MySQL database fails to be deployed (before start)".into(), + )); } // check app status @@ -344,7 +427,16 @@ impl Create for MySQL { aws_credentials_envs, ) { Ok(Some(true)) => {} - _ => return Err(ServiceError::OnCreateFailed), + _ => { + return Err(self.engine_error( + EngineErrorCause::Internal, + format!( + "MySQL database {} with id {} failed to start after several retries", + self.name(), + self.id() + ), + )); + } } } } @@ -352,12 +444,12 @@ impl Create for MySQL { Ok(()) } - fn on_create_check(&self) -> Result<(), ServiceError> { + fn on_create_check(&self) -> Result<(), EngineError> { //FIXME : perform an actual check Ok(()) } - fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { warn!("AWS.MySQL.on_create_error() called for {}", self.name()); self.delete(target, true) @@ -365,7 +457,7 @@ impl Create for MySQL { } impl Pause for MySQL { - fn on_pause(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_pause(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { info!("AWS.MySQL.on_pause() called for {}", self.name()); // TODO how to pause production? - the goal is to reduce cost, but it is possible to pause a production env? @@ -374,11 +466,11 @@ impl Pause for MySQL { Ok(()) } - fn on_pause_check(&self) -> Result<(), ServiceError> { + fn on_pause_check(&self) -> Result<(), EngineError> { Ok(()) } - fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { warn!("AWS.MySQL.on_pause_error() called for {}", self.name()); // TODO what to do if there is a pause error? @@ -388,85 +480,85 @@ impl Pause for MySQL { } impl Delete for MySQL { - fn on_delete(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { info!("AWS.MySQL.on_delete() called for {}", self.name()); self.delete(target, false) } - fn on_delete_check(&self) -> Result<(), ServiceError> { + fn on_delete_check(&self) -> Result<(), EngineError> { Ok(()) } - fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { warn!("AWS.MySQL.on_create_error() called for {}", self.name()); self.delete(target, true) } } impl crate::cloud_provider::service::Clone for MySQL { - fn on_clone(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_clone(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } - fn on_clone_check(&self) -> Result<(), ServiceError> { + fn on_clone_check(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_clone_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_clone_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } } impl Upgrade for MySQL { - fn on_upgrade(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_upgrade(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } - fn on_upgrade_check(&self) -> Result<(), ServiceError> { + fn on_upgrade_check(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_upgrade_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_upgrade_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } } impl Downgrade for MySQL { - fn on_downgrade(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_downgrade(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } - fn on_downgrade_check(&self) -> Result<(), ServiceError> { + fn on_downgrade_check(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_downgrade_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_downgrade_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } } impl Backup for MySQL { - fn on_backup(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_backup(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } - fn on_backup_check(&self) -> Result<(), ServiceError> { + fn on_backup_check(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_backup_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_backup_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } - fn on_restore(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_restore(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } - fn on_restore_check(&self) -> Result<(), ServiceError> { + fn on_restore_check(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_restore_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_restore_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } } diff --git a/src/cloud_provider/aws/databases/postgresql.rs b/src/cloud_provider/aws/databases/postgresql.rs index ef1a04d5..c0bffaff 100644 --- a/src/cloud_provider/aws/databases/postgresql.rs +++ b/src/cloud_provider/aws/databases/postgresql.rs @@ -6,10 +6,13 @@ use crate::cloud_provider::environment::Environment; use crate::cloud_provider::kubernetes::Kubernetes; use crate::cloud_provider::service::{ Action, Backup, Create, Database, DatabaseOptions, DatabaseType, Delete, Downgrade, Pause, - Service, ServiceError, ServiceType, StatefulService, Upgrade, + Service, ServiceType, StatefulService, Upgrade, }; use crate::cloud_provider::DeploymentTarget; use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY}; +use crate::error::{ + from_simple_error_to_engine_error, EngineError, EngineErrorCause, EngineErrorScope, +}; use crate::models::Context; pub struct PostgreSQL { @@ -69,18 +72,21 @@ impl PostgreSQL { fn tera_context(&self, kubernetes: &dyn Kubernetes, environment: &Environment) -> TeraContext { let mut context = self.default_tera_context(kubernetes, environment); + // FIXME: is there an other way than downcast a pointer? let cp = kubernetes .cloud_provider() .as_any() .downcast_ref::() .expect("Could not downcast kubernetes.cloud_provider() to AWS"); + // we need the kubernetes config file to store tfstates file in kube secrets let kubernetes_config_file_path = utilities::get_kubernetes_config_path( self.workspace_directory().as_str(), kubernetes, environment, ); + match kubernetes_config_file_path { Ok(kube_config) => { context.insert("kubeconfig_path", &kube_config.as_str()); @@ -92,8 +98,12 @@ impl PostgreSQL { utilities::create_namespace(&environment.namespace(), kube_config.as_str(), aws); } - Err(e) => error!("Failed to generate the kubernetes config file path: {}", e), + Err(e) => error!( + "Failed to generate the kubernetes config file path: {:?}", + e + ), } + context.insert("namespace", environment.namespace()); context.insert("aws_access_key", &cp.access_key_id); @@ -118,7 +128,7 @@ impl PostgreSQL { context } - fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), ServiceError> { + fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), EngineError> { let workspace_dir = self.workspace_directory(); match target { @@ -130,39 +140,58 @@ impl PostgreSQL { let context = self.tera_context(*kubernetes, *environment); - crate::template::generate_and_copy_all_files_into_dir( - format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(), - &workspace_dir, - &context, + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(), + &workspace_dir, + &context, + ), )?; - crate::template::generate_and_copy_all_files_into_dir( - format!("{}/aws/services/postgresql", self.context.lib_root_dir()).as_str(), - workspace_dir.as_str(), - &context, + + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!("{}/aws/services/postgresql", self.context.lib_root_dir()).as_str(), + workspace_dir.as_str(), + &context, + ), )?; - crate::template::generate_and_copy_all_files_into_dir( - format!( - "{}/aws/charts/external-name-svc", - self.context.lib_root_dir() - ) - .as_str(), - format!("{}/{}", workspace_dir, "external-name-svc").as_str(), - &context, + + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!( + "{}/aws/charts/external-name-svc", + self.context.lib_root_dir() + ) + .as_str(), + format!("{}/{}", workspace_dir, "external-name-svc").as_str(), + &context, + ), )?; - crate::template::generate_and_copy_all_files_into_dir( - format!( - "{}/aws/charts/external-name-svc", - self.context.lib_root_dir() - ) - .as_str(), - workspace_dir.as_str(), - &context, + + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!( + "{}/aws/charts/external-name-svc", + self.context.lib_root_dir() + ) + .as_str(), + workspace_dir.as_str(), + &context, + ), )?; match crate::cmd::terraform::terraform_exec_with_init_validate_destroy( workspace_dir.as_str(), ) { - Ok(o) => { + Ok(_) => { info!("Deleting secrets containing tfstates"); utilities::delete_terraform_tfstate_secret( *kubernetes, @@ -170,8 +199,16 @@ impl PostgreSQL { self.workspace_directory().as_str(), ); } - //TODO: find a way to raise the error - Err(e) => error!("Error while destroying infrastructure {}", e), + Err(e) => { + let message = format!( + "Error while destroying infrastructure {}", + e.message.unwrap_or("".into()) + ); + + error!("{}", message); + + return Err(self.engine_error(EngineErrorCause::Internal, message)); + } } } DeploymentTarget::SelfHosted(kubernetes, environment) => { @@ -179,20 +216,28 @@ impl PostgreSQL { let selector = format!("app={}", self.name()); if is_error { - let _ = common::get_stateless_resource_information( - *kubernetes, - *environment, - workspace_dir.as_str(), - selector.as_str(), + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + common::get_stateless_resource_information( + *kubernetes, + *environment, + workspace_dir.as_str(), + selector.as_str(), + ), )?; } // clean the resource - let _ = common::do_stateless_service_cleanup( - *kubernetes, - *environment, - workspace_dir.as_str(), - helm_release_name.as_str(), + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + common::do_stateless_service_cleanup( + *kubernetes, + *environment, + workspace_dir.as_str(), + helm_release_name.as_str(), + ), )?; } } @@ -248,7 +293,7 @@ impl Service for PostgreSQL { impl Database for PostgreSQL {} impl Create for PostgreSQL { - fn on_create(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { info!("AWS.PostgreSQL.on_create() called for {}", self.name()); let workspace_dir = self.workspace_directory(); @@ -261,29 +306,47 @@ impl Create for PostgreSQL { let workspace_dir = self.workspace_directory(); - crate::template::generate_and_copy_all_files_into_dir( - format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(), - &workspace_dir, - &context, - )?; - crate::template::generate_and_copy_all_files_into_dir( - format!("{}/aws/services/postgresql", self.context.lib_root_dir()).as_str(), - workspace_dir.as_str(), - &context, - )?; - crate::template::generate_and_copy_all_files_into_dir( - format!( - "{}/aws/charts/external-name-svc", - self.context.lib_root_dir() - ) - .as_str(), - format!("{}/{}", workspace_dir, "external-name-svc").as_str(), - &context, + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(), + &workspace_dir, + &context, + ), )?; - crate::cmd::terraform::terraform_exec_with_init_validate_plan_apply( - workspace_dir.as_str(), - false, + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!("{}/aws/services/postgresql", self.context.lib_root_dir()).as_str(), + workspace_dir.as_str(), + &context, + ), + )?; + + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + format!( + "{}/aws/charts/external-name-svc", + self.context.lib_root_dir() + ) + .as_str(), + format!("{}/{}", workspace_dir, "external-name-svc").as_str(), + &context, + ), + )?; + + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::cmd::terraform::terraform_exec_with_init_validate_plan_apply( + workspace_dir.as_str(), + false, + ), )?; } DeploymentTarget::SelfHosted(kubernetes, environment) => { @@ -298,22 +361,30 @@ impl Create for PostgreSQL { .downcast_ref::() .unwrap(); - let kubernetes_config_file_path = common::kubernetes_config_path( - workspace_dir.as_str(), - environment.organization_id.as_str(), - kubernetes.id(), - aws.access_key_id.as_str(), - aws.secret_access_key.as_str(), - kubernetes.region(), + let kubernetes_config_file_path = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + common::kubernetes_config_path( + workspace_dir.as_str(), + environment.organization_id.as_str(), + kubernetes.id(), + aws.access_key_id.as_str(), + aws.secret_access_key.as_str(), + kubernetes.region(), + ), )?; let from_dir = format!("{}/common/services/postgresql", self.context.lib_root_dir()); - let _ = crate::template::generate_and_copy_all_files_into_dir( - from_dir.as_str(), - workspace_dir.as_str(), - &context, + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + from_dir.as_str(), + workspace_dir.as_str(), + &context, + ), )?; // render templates @@ -324,19 +395,26 @@ impl Create for PostgreSQL { ]; // do exec helm upgrade and return the last deployment status - let helm_history_row = crate::cmd::helm::helm_exec_with_upgrade_history( - kubernetes_config_file_path.as_str(), - environment.namespace(), - helm_release_name.as_str(), - workspace_dir.as_str(), - aws_credentials_envs.clone(), + let helm_history_row = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::cmd::helm::helm_exec_with_upgrade_history( + kubernetes_config_file_path.as_str(), + environment.namespace(), + helm_release_name.as_str(), + workspace_dir.as_str(), + aws_credentials_envs.clone(), + ), )?; // check deployment status if helm_history_row.is_none() || !helm_history_row.unwrap().is_successfully_deployed() { - return Err(ServiceError::OnCreateFailed); + return Err(self.engine_error( + EngineErrorCause::Internal, + "PostgreSQL database fails to be deployed (before start)".into(), + )); } // check app status @@ -349,7 +427,16 @@ impl Create for PostgreSQL { aws_credentials_envs, ) { Ok(Some(true)) => {} - _ => return Err(ServiceError::OnCreateFailed), + _ => { + return Err(self.engine_error( + EngineErrorCause::Internal, + format!( + "PostgreSQL database {} with id {} failed to start after several retries", + self.name(), + self.id() + ), + )); + } } } } @@ -357,11 +444,11 @@ impl Create for PostgreSQL { Ok(()) } - fn on_create_check(&self) -> Result<(), ServiceError> { + fn on_create_check(&self) -> Result<(), EngineError> { Ok(()) } - fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { warn!( "AWS.PostgreSQL.on_create_error() called for {}", self.name() @@ -372,7 +459,7 @@ impl Create for PostgreSQL { } impl Pause for PostgreSQL { - fn on_pause(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_pause(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { info!("AWS.PostgreSQL.on_pause() called for {}", self.name()); // TODO how to pause production? - the goal is to reduce cost, but it is possible to pause a production env? @@ -381,11 +468,11 @@ impl Pause for PostgreSQL { Ok(()) } - fn on_pause_check(&self) -> Result<(), ServiceError> { + fn on_pause_check(&self) -> Result<(), EngineError> { Ok(()) } - fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { warn!("AWS.PostgreSQL.on_pause_error() called for {}", self.name()); // TODO what to do if there is a pause error? @@ -395,16 +482,16 @@ impl Pause for PostgreSQL { } impl Delete for PostgreSQL { - fn on_delete(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { info!("AWS.PostgreSQL.on_delete() called for {}", self.name()); self.delete(target, false) } - fn on_delete_check(&self) -> Result<(), ServiceError> { + fn on_delete_check(&self) -> Result<(), EngineError> { Ok(()) } - fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { warn!( "AWS.PostgreSQL.on_create_error() called for {}", self.name() @@ -414,69 +501,69 @@ impl Delete for PostgreSQL { } impl crate::cloud_provider::service::Clone for PostgreSQL { - fn on_clone(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_clone(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } - fn on_clone_check(&self) -> Result<(), ServiceError> { + fn on_clone_check(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_clone_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_clone_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } } impl Upgrade for PostgreSQL { - fn on_upgrade(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_upgrade(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } - fn on_upgrade_check(&self) -> Result<(), ServiceError> { + fn on_upgrade_check(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_upgrade_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_upgrade_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } } impl Downgrade for PostgreSQL { - fn on_downgrade(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_downgrade(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } - fn on_downgrade_check(&self) -> Result<(), ServiceError> { + fn on_downgrade_check(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_downgrade_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_downgrade_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } } impl Backup for PostgreSQL { - fn on_backup(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_backup(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } - fn on_backup_check(&self) -> Result<(), ServiceError> { + fn on_backup_check(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_backup_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_backup_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } - fn on_restore(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_restore(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } - fn on_restore_check(&self) -> Result<(), ServiceError> { + fn on_restore_check(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_restore_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_restore_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { unimplemented!() } } diff --git a/src/cloud_provider/aws/databases/utilities.rs b/src/cloud_provider/aws/databases/utilities.rs index a000355d..02337220 100644 --- a/src/cloud_provider/aws/databases/utilities.rs +++ b/src/cloud_provider/aws/databases/utilities.rs @@ -5,13 +5,14 @@ use crate::cloud_provider::environment::Environment; use crate::cloud_provider::kubernetes::Kubernetes; use crate::cmd::kubectl::{kubectl_exec_create_namespace, kubectl_exec_delete_secret}; use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY}; +use crate::error::SimpleError; // generate the kubernetes config path pub fn get_kubernetes_config_path( workspace: &str, kubernetes: &dyn Kubernetes, environment: &Environment, -) -> Result { +) -> Result { let aws = kubernetes .cloud_provider() .as_any() @@ -40,7 +41,7 @@ pub fn delete_terraform_tfstate_secret( kubernetes: &dyn Kubernetes, environment: &Environment, workspace_dir: &str, -) -> Result<(), Error> { +) -> Result<(), SimpleError> { let aws = kubernetes .cloud_provider() .as_any() @@ -67,7 +68,10 @@ pub fn delete_terraform_tfstate_secret( Ok(()) } Err(e) => { - error!("Failed to generate the kubernetes config file path: {}", e); + error!( + "Failed to generate the kubernetes config file path: {:?}", + e + ); Err(e) } } diff --git a/src/cloud_provider/aws/external_service.rs b/src/cloud_provider/aws/external_service.rs index a90bdd30..fc040066 100644 --- a/src/cloud_provider/aws/external_service.rs +++ b/src/cloud_provider/aws/external_service.rs @@ -6,11 +6,12 @@ use crate::cloud_provider::aws::{common, AWS}; use crate::cloud_provider::environment::Environment; use crate::cloud_provider::kubernetes::Kubernetes; use crate::cloud_provider::service::{ - Action, Application, Create, Delete, Pause, Service, ServiceError, ServiceType, - StatelessService, + Action, Application as AApplication, Create, Delete, ExternalService as EExternalService, + Pause, Service, ServiceType, StatelessService, }; use crate::cloud_provider::DeploymentTarget; use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY}; +use crate::error::{from_simple_error_to_engine_error, EngineError, EngineErrorCause}; use crate::models::Context; #[derive(Clone, Eq, PartialEq, Hash)] @@ -92,7 +93,7 @@ impl ExternalService { context } - fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), ServiceError> { + fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), EngineError> { let (kubernetes, environment) = match target { DeploymentTarget::ManagedServices(k, env) => (*k, *env), DeploymentTarget::SelfHosted(k, env) => (*k, *env), @@ -103,20 +104,28 @@ impl ExternalService { let selector = format!("app={}", self.name()); if is_error { - let _ = common::get_stateless_resource_information( - kubernetes, - environment, - workspace_dir.as_str(), - selector.as_str(), + let _ = from_simple_error_to_engine_error( + crate::cloud_provider::service::ExternalService::engine_error_scope(self), + self.context.execution_id(), + common::get_stateless_resource_information( + kubernetes, + environment, + workspace_dir.as_str(), + selector.as_str(), + ), )?; } // clean the resource - let _ = common::do_stateless_service_cleanup( - kubernetes, - environment, - workspace_dir.as_str(), - helm_release_name.as_str(), + let _ = from_simple_error_to_engine_error( + crate::cloud_provider::service::ExternalService::engine_error_scope(self), + self.context.execution_id(), + common::do_stateless_service_cleanup( + kubernetes, + environment, + workspace_dir.as_str(), + helm_release_name.as_str(), + ), )?; Ok(()) @@ -180,7 +189,7 @@ impl Service for ExternalService { } impl Create for ExternalService { - fn on_create(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { info!( "AWS.external_service.on_create() called for {}", self.name() @@ -200,10 +209,14 @@ impl Create for ExternalService { let workspace_dir = self.workspace_directory(); let from_dir = format!("{}/common/services/q-job", self.context.lib_root_dir()); - let _ = crate::template::generate_and_copy_all_files_into_dir( - from_dir.as_str(), - workspace_dir.as_str(), - &context, + let _ = from_simple_error_to_engine_error( + crate::cloud_provider::service::ExternalService::engine_error_scope(self), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + from_dir.as_str(), + workspace_dir.as_str(), + &context, + ), )?; // render @@ -214,28 +227,41 @@ impl Create for ExternalService { (AWS_SECRET_ACCESS_KEY, aws.secret_access_key.as_str()), ]; - let kubernetes_config_file_path = common::kubernetes_config_path( - workspace_dir.as_str(), - environment.organization_id.as_str(), - kubernetes.id(), - aws.access_key_id.as_str(), - aws.secret_access_key.as_str(), - kubernetes.region(), + let kubernetes_config_file_path = from_simple_error_to_engine_error( + crate::cloud_provider::service::ExternalService::engine_error_scope(self), + self.context.execution_id(), + common::kubernetes_config_path( + workspace_dir.as_str(), + environment.organization_id.as_str(), + kubernetes.id(), + aws.access_key_id.as_str(), + aws.secret_access_key.as_str(), + kubernetes.region(), + ), )?; // do exec helm upgrade and return the last deployment status - let helm_history_row = crate::cmd::helm::helm_exec_with_upgrade_history( - kubernetes_config_file_path.as_str(), - environment.namespace(), - helm_release_name.as_str(), - workspace_dir.as_str(), - aws_credentials_envs.clone(), + let helm_history_row = from_simple_error_to_engine_error( + crate::cloud_provider::service::ExternalService::engine_error_scope(self), + self.context.execution_id(), + crate::cmd::helm::helm_exec_with_upgrade_history( + kubernetes_config_file_path.as_str(), + environment.namespace(), + helm_release_name.as_str(), + workspace_dir.as_str(), + aws_credentials_envs.clone(), + ), )?; // check deployment status if helm_history_row.is_none() || !helm_history_row.unwrap().is_successfully_deployed() { - // TODO get pod output by using kubectl and return it into the OnCreateFailed - return Err(ServiceError::OnCreateFailed); + return Err(crate::cloud_provider::service::ExternalService::engine_error(self, EngineErrorCause::User( + "Your External Service didn't start for some reason. \ + Are you sure your External Service is correctly running? You can give a try by running \ + locally `docker run`. You can also check the External Service log from the web \ + interface or the CLI with `qovery log`", + ), format!("External Service {} has failed to start ⤬", self.name_with_id()), + )); } // check job status @@ -246,17 +272,29 @@ impl Create for ExternalService { aws_credentials_envs, ) { Ok(Some(true)) => {} - _ => return Err(ServiceError::OnCreateFailed), + _ => { + return Err( + crate::cloud_provider::service::ExternalService::engine_error( + self, + EngineErrorCause::Internal, + format!( + "External Service {} with id {} failed to start after several retries", + self.name(), + self.id() + ), + ), + ); + } } Ok(()) } - fn on_create_check(&self) -> Result<(), ServiceError> { + fn on_create_check(&self) -> Result<(), EngineError> { Ok(()) } - fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { warn!( "AWS.external_service.on_create_error() called for {}", self.name() @@ -279,30 +317,42 @@ impl Create for ExternalService { (AWS_SECRET_ACCESS_KEY, aws.secret_access_key.as_str()), ]; - let kubernetes_config_file_path = common::kubernetes_config_path( - workspace_dir.as_str(), - environment.organization_id.as_str(), - kubernetes.id(), - aws.access_key_id.as_str(), - aws.secret_access_key.as_str(), - kubernetes.region(), + let kubernetes_config_file_path = from_simple_error_to_engine_error( + crate::cloud_provider::service::ExternalService::engine_error_scope(self), + self.context.execution_id(), + common::kubernetes_config_path( + workspace_dir.as_str(), + environment.organization_id.as_str(), + kubernetes.id(), + aws.access_key_id.as_str(), + aws.secret_access_key.as_str(), + kubernetes.region(), + ), )?; let helm_release_name = self.helm_release_name(); - let history_rows = crate::cmd::helm::helm_exec_history( - kubernetes_config_file_path.as_str(), - environment.namespace(), - helm_release_name.as_str(), - aws_credentials_envs.clone(), - )?; - - if history_rows.len() == 1 { - crate::cmd::helm::helm_exec_uninstall( + let history_rows = from_simple_error_to_engine_error( + crate::cloud_provider::service::ExternalService::engine_error_scope(self), + self.context.execution_id(), + crate::cmd::helm::helm_exec_history( kubernetes_config_file_path.as_str(), environment.namespace(), helm_release_name.as_str(), aws_credentials_envs.clone(), + ), + )?; + + if history_rows.len() == 1 { + from_simple_error_to_engine_error( + crate::cloud_provider::service::ExternalService::engine_error_scope(self), + self.context.execution_id(), + crate::cmd::helm::helm_exec_uninstall( + kubernetes_config_file_path.as_str(), + environment.namespace(), + helm_release_name.as_str(), + aws_credentials_envs.clone(), + ), )?; } @@ -311,16 +361,16 @@ impl Create for ExternalService { } impl Pause for ExternalService { - fn on_pause(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { info!("AWS.external_service.on_pause() called for {}", self.name()); self.delete(target, false) } - fn on_pause_check(&self) -> Result<(), ServiceError> { + fn on_pause_check(&self) -> Result<(), EngineError> { Ok(()) } - fn on_pause_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_pause_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { warn!( "AWS.external_service.on_pause_error() called for {}", self.name() @@ -330,7 +380,7 @@ impl Pause for ExternalService { } impl Delete for ExternalService { - fn on_delete(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { info!( "AWS.external_service.on_delete() called for {}", self.name() @@ -338,11 +388,11 @@ impl Delete for ExternalService { self.delete(target, false) } - fn on_delete_check(&self) -> Result<(), ServiceError> { + fn on_delete_check(&self) -> Result<(), EngineError> { Ok(()) } - fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { warn!( "AWS.external_service.on_delete_error() called for {}", self.name() diff --git a/src/cloud_provider/aws/kubernetes/mod.rs b/src/cloud_provider/aws/kubernetes/mod.rs index f3d03d91..e92fe259 100644 --- a/src/cloud_provider/aws/kubernetes/mod.rs +++ b/src/cloud_provider/aws/kubernetes/mod.rs @@ -16,20 +16,20 @@ use crate::cloud_provider::aws::kubernetes::node::Node; use crate::cloud_provider::aws::{common, AWS}; use crate::cloud_provider::environment::Environment; use crate::cloud_provider::kubernetes::{ - check_kubernetes_has_enough_resources_to_deploy_environment, Kind, Kubernetes, KubernetesError, - KubernetesNode, Resources, + check_kubernetes_has_enough_resources_to_deploy_environment, Kind, Kubernetes, KubernetesNode, + Resources, }; use crate::cloud_provider::service::{Service, ServiceType}; use crate::cloud_provider::{CloudProvider, DeploymentTarget}; use crate::cmd; use crate::cmd::helm::helm_uninstall_list; use crate::cmd::kubectl::{kubectl_exec_delete_namespace, kubectl_exec_get_all_namespaces}; -use crate::cmd::utilities::CmdError; use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY}; use crate::deletion_utilities::{get_firsts_namespaces_to_delete, get_qovery_managed_namespaces}; use crate::dns_provider::cloudflare::Cloudflare; use crate::dns_provider::DnsProvider; use crate::dns_provider::Kind::CLOUDFLARE; +use crate::error::{from_simple_error_to_engine_error, EngineError, EngineErrorCause}; use crate::fs::workspace_directory; use crate::models::{ Context, Listener, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressListener, @@ -348,7 +348,7 @@ impl<'a> Kubernetes for EKS<'a> { self.dns_provider } - fn is_valid(&self) -> Result<(), KubernetesError> { + fn is_valid(&self) -> Result<(), EngineError> { Ok(()) } @@ -360,20 +360,24 @@ impl<'a> Kubernetes for EKS<'a> { &self.listeners } - fn resources(&self, environment: &Environment) -> Result { + fn resources(&self, environment: &Environment) -> Result { let aws = self .cloud_provider() .as_any() .downcast_ref::() .unwrap(); - let kubernetes_config_file_path = common::kubernetes_config_path( - self.context.workspace_root_dir(), - environment.organization_id.as_str(), - &self.id, - aws.access_key_id.as_str(), - aws.secret_access_key.as_str(), - self.region(), + let kubernetes_config_file_path = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + common::kubernetes_config_path( + self.context.workspace_root_dir(), + environment.organization_id.as_str(), + &self.id, + aws.access_key_id.as_str(), + aws.secret_access_key.as_str(), + self.region(), + ), )?; let aws_credentials_envs = vec![ @@ -381,8 +385,11 @@ impl<'a> Kubernetes for EKS<'a> { (AWS_SECRET_ACCESS_KEY, aws.secret_access_key.as_str()), ]; - let nodes = - cmd::kubectl::kubectl_exec_get_node(kubernetes_config_file_path, aws_credentials_envs)?; + let nodes = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + cmd::kubectl::kubectl_exec_get_node(kubernetes_config_file_path, aws_credentials_envs), + )?; let mut resources = Resources { free_cpu: 0.0, @@ -413,7 +420,7 @@ impl<'a> Kubernetes for EKS<'a> { Ok(resources) } - fn on_create(&self) -> Result<(), KubernetesError> { + fn on_create(&self) -> Result<(), EngineError> { info!("EKS.on_create() called for {}", self.name()); let listeners_helper = ListenersHelper::new(&self.listeners); @@ -439,57 +446,78 @@ impl<'a> Kubernetes for EKS<'a> { // generate terraform files and copy them into temp dir let context = self.tera_context(); - let _ = crate::template::generate_and_copy_all_files_into_dir( - self.template_directory.as_str(), - temp_dir.as_str(), - &context, + + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + self.template_directory.as_str(), + temp_dir.as_str(), + &context, + ), )?; // copy lib/common/bootstrap/charts directory (and sub directory) into the lib/aws/bootstrap/common/charts directory. // this is due to the required dependencies of lib/aws/bootstrap/*.tf files let common_charts_temp_dir = format!("{}/common/charts", temp_dir.as_str()); - let _ = crate::template::copy_non_template_files( - format!("{}/common/bootstrap/charts", self.context.lib_root_dir()), - common_charts_temp_dir.as_str(), + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::copy_non_template_files( + format!("{}/common/bootstrap/charts", self.context.lib_root_dir()), + common_charts_temp_dir.as_str(), + ), )?; - let _ = crate::cmd::terraform::terraform_exec_with_init_validate_plan_apply( - temp_dir.as_str(), - true, + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::cmd::terraform::terraform_exec_with_init_validate_plan_apply( + temp_dir.as_str(), + true, + ), )?; Ok(()) } - fn on_create_error(&self) -> Result<(), KubernetesError> { + fn on_create_error(&self) -> Result<(), EngineError> { warn!("EKS.on_create_error() called for {}", self.name()); // FIXME - Err(KubernetesError::Error) + Err(self.engine_error( + EngineErrorCause::Internal, + format!( + "{} Kubernetes cluster rollback not implemented", + self.name() + ), + )) } - fn on_upgrade(&self) -> Result<(), KubernetesError> { + fn on_upgrade(&self) -> Result<(), EngineError> { info!("EKS.on_upgrade() called for {}", self.name()); unimplemented!() } - fn on_upgrade_error(&self) -> Result<(), KubernetesError> { + fn on_upgrade_error(&self) -> Result<(), EngineError> { warn!("EKS.on_upgrade_error() called for {}", self.name()); unimplemented!() } - fn on_downgrade(&self) -> Result<(), KubernetesError> { + fn on_downgrade(&self) -> Result<(), EngineError> { info!("EKS.on_downgrade() called for {}", self.name()); unimplemented!() } - fn on_downgrade_error(&self) -> Result<(), KubernetesError> { + fn on_downgrade_error(&self) -> Result<(), EngineError> { warn!("EKS.on_downgrade_error() called for {}", self.name()); unimplemented!() } - fn on_delete(&self) -> Result<(), KubernetesError> { + fn on_delete(&self) -> Result<(), EngineError> { info!("EKS.on_delete() called for {}", self.name()); + let listeners_helper = ListenersHelper::new(&self.listeners); + listeners_helper.delete_in_progress(ProgressInfo::new( ProgressScope::Infrastructure { execution_id: self.context.execution_id().to_string(), @@ -511,19 +539,30 @@ impl<'a> Kubernetes for EKS<'a> { // generate terraform files and copy them into temp dir let context = self.tera_context(); - let _ = crate::template::generate_and_copy_all_files_into_dir( - self.template_directory.as_str(), - temp_dir.as_str(), - &context, + + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + self.template_directory.as_str(), + temp_dir.as_str(), + &context, + ), )?; // copy lib/common/bootstrap/charts directory (and sub directory) into the lib/aws/bootstrap/common/charts directory. // this is due to the required dependencies of lib/aws/bootstrap/*.tf files let common_charts_temp_dir = format!("{}/common/charts", temp_dir.as_str()); - let _ = crate::template::copy_non_template_files( - format!("{}/common/bootstrap/charts", self.context.lib_root_dir()), - common_charts_temp_dir.as_str(), + + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::copy_non_template_files( + format!("{}/common/bootstrap/charts", self.context.lib_root_dir()), + common_charts_temp_dir.as_str(), + ), )?; + let aws_credentials_envs = vec![ ( AWS_ACCESS_KEY_ID, @@ -535,14 +574,19 @@ impl<'a> Kubernetes for EKS<'a> { ), ]; - let kubernetes_config_file_path = kubernetes_config_path( - self.context.workspace_root_dir(), - self.cloud_provider.organization_id.as_str(), - self.id(), - self.cloud_provider.access_key_id.as_str(), - self.cloud_provider.secret_access_key.as_str(), - self.region(), + let kubernetes_config_file_path = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + kubernetes_config_path( + self.context.workspace_root_dir(), + self.cloud_provider.organization_id.as_str(), + self.id(), + self.cloud_provider.access_key_id.as_str(), + self.cloud_provider.secret_access_key.as_str(), + self.region(), + ), )?; + let all_namespaces = kubectl_exec_get_all_namespaces( kubernetes_config_file_path, aws_credentials_envs.clone(), @@ -553,15 +597,20 @@ impl<'a> Kubernetes for EKS<'a> { Ok(namespace_vec) => { let namespaces_as_str = namespace_vec.iter().map(std::ops::Deref::deref).collect(); let namespaces_to_delete = get_firsts_namespaces_to_delete(namespaces_as_str); + info!("Deleting non Qovery Namespaces"); for namespace_to_delete in namespaces_to_delete.iter() { - let kubernetes_config_file_path0 = kubernetes_config_path( - self.context.workspace_root_dir(), - self.cloud_provider.organization_id.as_str(), - self.id(), - self.cloud_provider.access_key_id.as_str(), - self.cloud_provider.secret_access_key.as_str(), - self.region(), + let kubernetes_config_file_path0 = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + kubernetes_config_path( + self.context.workspace_root_dir(), + self.cloud_provider.organization_id.as_str(), + self.id(), + self.cloud_provider.access_key_id.as_str(), + self.cloud_provider.secret_access_key.as_str(), + self.region(), + ), )?; let deletion = cmd::kubectl::kubectl_exec_delete_namespace( @@ -569,9 +618,10 @@ impl<'a> Kubernetes for EKS<'a> { namespace_to_delete, aws_credentials_envs.clone(), ); + match deletion { - Ok(out) => info!("Namespace {} is deleted", namespace_to_delete), - Err(e) => { + Ok(_) => info!("Namespace {} is deleted", namespace_to_delete), + Err(_) => { error!( "Can't delete the namespace {}, quiting now", namespace_to_delete @@ -580,17 +630,29 @@ impl<'a> Kubernetes for EKS<'a> { } } } - Err(e) => error!("Error while getting all namespaces {}", e), + + Err(e) => error!( + "Error while getting all namespaces for Kubernetes cluster {}: error {:?}", + self.name_with_id(), + e.message + ), } + info!("Deleting Qovery managed Namespaces"); - let kubernetes_config_file_path2 = kubernetes_config_path( - self.context.workspace_root_dir(), - self.cloud_provider.organization_id.as_str(), - self.id(), - self.cloud_provider.access_key_id.as_str(), - self.cloud_provider.secret_access_key.as_str(), - self.region(), + + let kubernetes_config_file_path2 = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + kubernetes_config_path( + self.context.workspace_root_dir(), + self.cloud_provider.organization_id.as_str(), + self.id(), + self.cloud_provider.access_key_id.as_str(), + self.cloud_provider.secret_access_key.as_str(), + self.region(), + ), )?; + // TODO use label instead fixed names let mut qovery_namespaces = get_qovery_managed_namespaces(); for qovery_namespace in qovery_namespaces.iter() { @@ -600,8 +662,8 @@ impl<'a> Kubernetes for EKS<'a> { aws_credentials_envs.clone(), ); match deletion { - Ok(out) => info!("Namespace {} is fully deleted", qovery_namespace), - Err(e) => { + Ok(_) => info!("Namespace {} is fully deleted", qovery_namespace), + Err(_) => { error!( "Can't delete the namespace {}, quiting now", qovery_namespace @@ -609,6 +671,7 @@ impl<'a> Kubernetes for EKS<'a> { } } } + info!("Delete all remaining deployed helm applications"); match cmd::helm::helm_list(&kubernetes_config_file_path2, aws_credentials_envs.clone()) { @@ -619,15 +682,20 @@ impl<'a> Kubernetes for EKS<'a> { aws_credentials_envs.clone(), ); } - Err(e) => error!("Unable to get helm list"), + Err(_) => error!("Unable to get helm list"), } + info!("Running Terraform destroy"); - let terraform_result = - cmd::terraform::terraform_exec_with_init_validate_destroy(temp_dir.as_str())?; + let terraform_result = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + cmd::terraform::terraform_exec_with_init_validate_destroy(temp_dir.as_str()), + ); + // we should delete the bucket containing the kubeconfig after // to prevent to loose connection from terraform to kube cluster match terraform_result { - () => { + Ok(_) => { info!("Deleting S3 Bucket containing Kubeconfig"); let s3_kubeconfig_bucket = get_s3_kubeconfig_bucket_name(self.id.clone()); let _region = Region::from_str(self.region()).unwrap(); @@ -645,7 +713,7 @@ impl<'a> Kubernetes for EKS<'a> { Ok(()) } - fn on_delete_error(&self) -> Result<(), KubernetesError> { + fn on_delete_error(&self) -> Result<(), EngineError> { warn!("EKS.on_delete_error() called for {}", self.name()); // FIXME What should we do if something goes wrong while deleting the cluster? @@ -653,7 +721,7 @@ impl<'a> Kubernetes for EKS<'a> { Ok(()) } - fn deploy_environment(&self, environment: &Environment) -> Result<(), KubernetesError> { + fn deploy_environment(&self, environment: &Environment) -> Result<(), EngineError> { info!("EKS.deploy_environment() called for {}", self.name()); let listeners_helper = ListenersHelper::new(&self.listeners); @@ -706,7 +774,7 @@ impl<'a> Kubernetes for EKS<'a> { self.context.execution_id(), )); - return Err(KubernetesError::Deploy(err)); + return Err(err); } _ => { listeners_helper.start_in_progress(ProgressInfo::new( @@ -761,7 +829,7 @@ impl<'a> Kubernetes for EKS<'a> { self.context.execution_id(), )); - return Err(KubernetesError::Deploy(err)); + return Err(err); } _ => { listeners_helper.start_in_progress(ProgressInfo::new( @@ -814,7 +882,7 @@ impl<'a> Kubernetes for EKS<'a> { self.context.execution_id(), )); - return Err(KubernetesError::Deploy(err)); + return Err(err); } _ => { listeners_helper.started(ProgressInfo::new( @@ -866,7 +934,7 @@ impl<'a> Kubernetes for EKS<'a> { self.context.execution_id(), )); - return Err(KubernetesError::Deploy(err)); + return Err(err); } _ => { listeners_helper.start_in_progress(ProgressInfo::new( @@ -886,7 +954,7 @@ impl<'a> Kubernetes for EKS<'a> { Ok(()) } - fn deploy_environment_error(&self, environment: &Environment) -> Result<(), KubernetesError> { + fn deploy_environment_error(&self, environment: &Environment) -> Result<(), EngineError> { warn!("EKS.deploy_environment_error() called for {}", self.name()); let listeners_helper = ListenersHelper::new(&self.listeners); @@ -947,7 +1015,7 @@ impl<'a> Kubernetes for EKS<'a> { self.context.execution_id(), )); - return Err(KubernetesError::Deploy(err)); + return Err(err); } _ => { listeners_helper.start_in_progress(ProgressInfo::new( @@ -1002,7 +1070,7 @@ impl<'a> Kubernetes for EKS<'a> { self.context.execution_id(), )); - return Err(KubernetesError::Deploy(err)); + return Err(err); } _ => { listeners_helper.start_in_progress(ProgressInfo::new( @@ -1022,7 +1090,7 @@ impl<'a> Kubernetes for EKS<'a> { Ok(()) } - fn pause_environment(&self, environment: &Environment) -> Result<(), KubernetesError> { + fn pause_environment(&self, environment: &Environment) -> Result<(), EngineError> { info!("EKS.pause_environment() called for {}", self.name()); let listeners_helper = ListenersHelper::new(&self.listeners); @@ -1047,7 +1115,7 @@ impl<'a> Kubernetes for EKS<'a> { err ); - return Err(KubernetesError::Pause(err)); + return Err(err); } _ => {} } @@ -1066,7 +1134,7 @@ impl<'a> Kubernetes for EKS<'a> { err ); - return Err(KubernetesError::Pause(err)); + return Err(err); } _ => {} } @@ -1083,7 +1151,7 @@ impl<'a> Kubernetes for EKS<'a> { err ); - return Err(KubernetesError::Pause(err)); + return Err(err); } _ => {} } @@ -1099,7 +1167,7 @@ impl<'a> Kubernetes for EKS<'a> { err ); - return Err(KubernetesError::Pause(err)); + return Err(err); } _ => {} } @@ -1108,12 +1176,12 @@ impl<'a> Kubernetes for EKS<'a> { Ok(()) } - fn pause_environment_error(&self, _environment: &Environment) -> Result<(), KubernetesError> { + fn pause_environment_error(&self, _environment: &Environment) -> Result<(), EngineError> { warn!("EKS.pause_environment_error() called for {}", self.name()); Ok(()) } - fn delete_environment(&self, environment: &Environment) -> Result<(), KubernetesError> { + fn delete_environment(&self, environment: &Environment) -> Result<(), EngineError> { info!("EKS.delete_environment() called for {}", self.name()); let listeners_helper = ListenersHelper::new(&self.listeners); @@ -1139,7 +1207,7 @@ impl<'a> Kubernetes for EKS<'a> { err ); - return Err(KubernetesError::Delete(err)); + return Err(err); } _ => {} } @@ -1158,7 +1226,7 @@ impl<'a> Kubernetes for EKS<'a> { err ); - return Err(KubernetesError::Delete(err)); + return Err(err); } _ => {} } @@ -1175,7 +1243,7 @@ impl<'a> Kubernetes for EKS<'a> { err ); - return Err(KubernetesError::Delete(err)); + return Err(err); } _ => {} } @@ -1191,7 +1259,7 @@ impl<'a> Kubernetes for EKS<'a> { err ); - return Err(KubernetesError::Delete(err)); + return Err(err); } _ => {} } @@ -1208,13 +1276,17 @@ impl<'a> Kubernetes for EKS<'a> { ), ]; - let kubernetes_config_file_path = common::kubernetes_config_path( - &self.context.workspace_root_dir(), - &environment.organization_id.as_str(), - &self.id, - &self.cloud_provider.access_key_id.as_str(), - &self.cloud_provider.secret_access_key.as_str(), - &self.region.name(), + let kubernetes_config_file_path = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + common::kubernetes_config_path( + &self.context.workspace_root_dir(), + &environment.organization_id.as_str(), + &self.id, + &self.cloud_provider.access_key_id.as_str(), + &self.cloud_provider.secret_access_key.as_str(), + &self.region.name(), + ), )?; kubectl_exec_delete_namespace( @@ -1226,7 +1298,7 @@ impl<'a> Kubernetes for EKS<'a> { Ok(()) } - fn delete_environment_error(&self, _environment: &Environment) -> Result<(), KubernetesError> { + fn delete_environment_error(&self, _environment: &Environment) -> Result<(), EngineError> { warn!("EKS.delete_environment_error() called for {}", self.name()); Ok(()) } diff --git a/src/cloud_provider/aws/mod.rs b/src/cloud_provider/aws/mod.rs index 3e56f6fb..6f2e045f 100644 --- a/src/cloud_provider/aws/mod.rs +++ b/src/cloud_provider/aws/mod.rs @@ -5,7 +5,8 @@ use rusoto_core::{Client, HttpClient, Region}; use rusoto_credential::StaticProvider; use rusoto_sts::{GetCallerIdentityRequest, Sts, StsClient}; -use crate::cloud_provider::{CloudProvider, CloudProviderError, Kind, TerraformStateCredentials}; +use crate::cloud_provider::{CloudProvider, EngineError, Kind, TerraformStateCredentials}; +use crate::error::EngineErrorCause; use crate::models::{Context, Listener, Listeners, ProgressListener}; use crate::runtime::async_run; @@ -85,13 +86,20 @@ impl CloudProvider for AWS { self.name.as_str() } - fn is_valid(&self) -> Result<(), CloudProviderError> { + fn is_valid(&self) -> Result<(), EngineError> { let client = StsClient::new_with_client(self.client(), Region::default()); let s = async_run(client.get_caller_identity(GetCallerIdentityRequest::default())); match s { Ok(_x) => Ok(()), - Err(err) => Err(CloudProviderError::from(err)), + Err(_) => { + return Err( + self.engine_error( + EngineErrorCause::User("Your AWS account seems to be no longer valid (bad Credentials). \ + Please contact your Organization administrator to fix or change the Credentials."), + format!("failed to login to AWS {}", self.name_with_id())) + ); + } } } diff --git a/src/cloud_provider/aws/router.rs b/src/cloud_provider/aws/router.rs index 5eca4ad8..431b1d6c 100644 --- a/src/cloud_provider/aws/router.rs +++ b/src/cloud_provider/aws/router.rs @@ -1,21 +1,24 @@ -use crate::cloud_provider::aws::{common, AWS}; -use crate::cloud_provider::environment::Environment; -use crate::cloud_provider::kubernetes::Kubernetes; -use crate::cloud_provider::service::{ - Action, Create, Delete, Pause, Router as RRouter, Service, ServiceError, ServiceType, - StatelessService, -}; -use crate::cloud_provider::DeploymentTarget; -use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY}; -use crate::models::{ - Context, Listeners, ListenersHelper, Metadata, ProgressInfo, ProgressLevel, ProgressScope, -}; use dns_lookup::lookup_host; use retry::delay::{Fibonacci, Fixed}; use retry::OperationResult; use serde::{Deserialize, Serialize}; use tera::Context as TeraContext; +use crate::cloud_provider::aws::{common, AWS}; +use crate::cloud_provider::environment::Environment; +use crate::cloud_provider::kubernetes::Kubernetes; +use crate::cloud_provider::service::{ + Action, Create, Delete, Pause, Router as RRouter, Service, ServiceType, StatelessService, +}; +use crate::cloud_provider::DeploymentTarget; +use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY}; +use crate::error::{ + from_simple_error_to_engine_error, EngineError, EngineErrorCause, SimpleError, SimpleErrorKind, +}; +use crate::models::{ + Context, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope, +}; + pub struct Router { context: Context, id: String, @@ -219,7 +222,7 @@ impl Router { context } - fn delete(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let (kubernetes, environment) = match target { DeploymentTarget::ManagedServices(k, env) => (*k, *env), DeploymentTarget::SelfHosted(k, env) => (*k, *env), @@ -228,11 +231,15 @@ impl Router { let workspace_dir = self.workspace_directory(); let helm_release_name = self.helm_release_name(); - let _ = common::do_stateless_service_cleanup( - kubernetes, - environment, - workspace_dir.as_str(), - helm_release_name.as_str(), + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + common::do_stateless_service_cleanup( + kubernetes, + environment, + workspace_dir.as_str(), + helm_release_name.as_str(), + ), )?; Ok(()) @@ -282,7 +289,7 @@ impl Service for Router { } impl crate::cloud_provider::service::Router for Router { - fn check_domains(&self) -> Result<(), ServiceError> { + fn check_domains(&self) -> Result<(), EngineError> { let check_result = retry::retry(Fibonacci::from_millis(3000).take(10), || { // TODO send information back to the core info!("check custom domain {}", self.default_domain.as_str()); @@ -299,7 +306,15 @@ impl crate::cloud_provider::service::Router for Router { match check_result { Ok(_) => {} - Err(_) => return Err(ServiceError::CheckFailed), + Err(_) => { + return Err(self.engine_error( + EngineErrorCause::Internal, + format!( + "domain {} is still not ready after several retries", + self.default_domain.as_str() + ), + )) + } } Ok(()) @@ -309,7 +324,7 @@ impl crate::cloud_provider::service::Router for Router { impl StatelessService for Router {} impl Create for Router { - fn on_create(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { info!("AWS.router.on_create() called for {}", self.name()); let (kubernetes, environment) = match target { DeploymentTarget::ManagedServices(k, env) => (*k, *env), @@ -325,13 +340,17 @@ impl Create for Router { let workspace_dir = self.workspace_directory(); let helm_release_name = self.helm_release_name(); - let kubernetes_config_file_path = common::kubernetes_config_path( - workspace_dir.as_str(), - environment.organization_id.as_str(), - kubernetes.id(), - aws.access_key_id.as_str(), - aws.secret_access_key.as_str(), - kubernetes.region(), + let kubernetes_config_file_path = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + common::kubernetes_config_path( + workspace_dir.as_str(), + environment.organization_id.as_str(), + kubernetes.id(), + aws.access_key_id.as_str(), + aws.secret_access_key.as_str(), + kubernetes.region(), + ), )?; // respect order - getting the context here and not before is mandatory @@ -349,33 +368,50 @@ impl Create for Router { ); let from_dir = format!("{}/common/chart_values", self.context.lib_root_dir()); - let _ = crate::template::generate_and_copy_all_files_into_dir( - from_dir.as_str(), - into_dir.as_str(), - &context, + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + from_dir.as_str(), + into_dir.as_str(), + &context, + ), )?; - let _ = crate::template::copy_non_template_files( - format!( - "{}/common/charts/nginx-ingress", - self.context().lib_root_dir() + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::copy_non_template_files( + format!( + "{}/common/charts/nginx-ingress", + self.context().lib_root_dir() + ), + into_dir.as_str(), ), - into_dir.as_str(), )?; + // do exec helm upgrade and return the last deployment status - let helm_history_row = crate::cmd::helm::helm_exec_with_upgrade_history_with_override( - kubernetes_config_file_path.as_str(), - environment.namespace(), - format!("custom-{}", helm_release_name).as_str(), - into_dir.as_str(), - format!("{}/nginx-ingress.yaml", into_dir.as_str()).as_str(), - self.aws_credentials_envs(aws).to_vec(), + let helm_history_row = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::cmd::helm::helm_exec_with_upgrade_history_with_override( + kubernetes_config_file_path.as_str(), + environment.namespace(), + format!("custom-{}", helm_release_name).as_str(), + into_dir.as_str(), + format!("{}/nginx-ingress.yaml", into_dir.as_str()).as_str(), + self.aws_credentials_envs(aws).to_vec(), + ), )?; // check deployment status if helm_history_row.is_none() || !helm_history_row.unwrap().is_successfully_deployed() { - return Err(ServiceError::OnCreateFailed); + return Err(self.engine_error( + EngineErrorCause::Internal, + "Router has failed to be deployed".into(), + )); } + // waiting for the nlb, it should be deploy to get fqdn let external_ingress_hostname_custom_result = retry::retry(Fibonacci::from_millis(3000).take(10), || { @@ -390,6 +426,7 @@ impl Create for Router { .as_str(), self.aws_credentials_envs(aws).to_vec(), ); + match external_ingress_hostname_custom { Ok(external_ingress_hostname_custom) => { OperationResult::Ok(external_ingress_hostname_custom) @@ -402,38 +439,51 @@ impl Create for Router { } } }); + match external_ingress_hostname_custom_result { Ok(elb) => { //put it in the context context.insert("nlb_ingress_hostname", &elb); } - Err(e) => error!("Error getting the NLB endpoint to be able to configure TLS"), + Err(_) => error!("Error getting the NLB endpoint to be able to configure TLS"), } } + let from_dir = format!("{}/aws/charts/q-ingress-tls", self.context.lib_root_dir()); - let _ = crate::template::generate_and_copy_all_files_into_dir( - from_dir.as_str(), - workspace_dir.as_str(), - &context, + let _ = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::template::generate_and_copy_all_files_into_dir( + from_dir.as_str(), + workspace_dir.as_str(), + &context, + ), )?; // do exec helm upgrade and return the last deployment status - let helm_history_row = crate::cmd::helm::helm_exec_with_upgrade_history( - kubernetes_config_file_path.as_str(), - environment.namespace(), - helm_release_name.as_str(), - workspace_dir.as_str(), - self.aws_credentials_envs(aws).to_vec(), + let helm_history_row = from_simple_error_to_engine_error( + self.engine_error_scope(), + self.context.execution_id(), + crate::cmd::helm::helm_exec_with_upgrade_history( + kubernetes_config_file_path.as_str(), + environment.namespace(), + helm_release_name.as_str(), + workspace_dir.as_str(), + self.aws_credentials_envs(aws).to_vec(), + ), )?; if helm_history_row.is_none() || !helm_history_row.unwrap().is_successfully_deployed() { - return Err(ServiceError::OnCreateFailed); + return Err(self.engine_error( + EngineErrorCause::Internal, + "Router has failed to be deployed".into(), + )); } Ok(()) } - fn on_create_check(&self) -> Result<(), ServiceError> { + fn on_create_check(&self) -> Result<(), EngineError> { let check_result = retry::retry(Fixed::from_millis(3000).take(60), || { let rs_ips = lookup_host(self.default_domain.as_str()); match rs_ips { @@ -447,58 +497,63 @@ impl Create for Router { } } }); + match check_result { - Ok(out) => Ok(()), - Err(e) => { - error!("While checking the DNS propagation"); + Ok(_) => Ok(()), + Err(_) => { + let message = "While checking the DNS propagation"; + error!("{}", message); + let listeners_helper = ListenersHelper::new(&self.listeners); + listeners_helper.error(ProgressInfo::new( ProgressScope::Router { - id: self.id.to_string(), + id: self.id().into(), }, ProgressLevel::Error, Some("DNS propagation goes wrong."), self.context.execution_id(), )); - Err(ServiceError::CheckFailed) + + Err(self.engine_error(EngineErrorCause::Internal, message.into())) } } } - fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { warn!("AWS.router.on_create_error() called for {}", self.name()); self.delete(target) } } impl Pause for Router { - fn on_pause(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { info!("AWS.router.on_pause() called for {}", self.name()); self.delete(target) } - fn on_pause_check(&self) -> Result<(), ServiceError> { + fn on_pause_check(&self) -> Result<(), EngineError> { warn!("AWS.router.on_pause_error() called for {}", self.name()); // TODO check resource has been cleaned? Ok(()) } - fn on_pause_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_pause_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { self.delete(target) } } impl Delete for Router { - fn on_delete(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { info!("AWS.router.on_delete() called for {}", self.name()); self.delete(target) } - fn on_delete_check(&self) -> Result<(), ServiceError> { + fn on_delete_check(&self) -> Result<(), EngineError> { Ok(()) } - fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> { + fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { warn!("AWS.router.on_delete_error() called for {}", self.name()); self.delete(target) } diff --git a/src/cloud_provider/digitalocean/mod.rs b/src/cloud_provider/digitalocean/mod.rs index f2cb5fe8..3a31c001 100644 --- a/src/cloud_provider/digitalocean/mod.rs +++ b/src/cloud_provider/digitalocean/mod.rs @@ -5,7 +5,8 @@ use std::rc::Rc; use digitalocean::DigitalOcean; -use crate::cloud_provider::{CloudProvider, CloudProviderError, Kind, TerraformStateCredentials}; +use crate::cloud_provider::{CloudProvider, Kind, TerraformStateCredentials}; +use crate::error::EngineError; use crate::models::{Context, Listener, ProgressListener}; pub struct DO { @@ -49,7 +50,7 @@ impl CloudProvider for DO { unimplemented!() } - fn is_valid(&self) -> Result<(), CloudProviderError> { + fn is_valid(&self) -> Result<(), EngineError> { unimplemented!() } diff --git a/src/cloud_provider/environment.rs b/src/cloud_provider/environment.rs index e2c33bd9..7a5d6c5d 100644 --- a/src/cloud_provider/environment.rs +++ b/src/cloud_provider/environment.rs @@ -1,4 +1,5 @@ -use crate::cloud_provider::service::{ServiceError, StatefulService, StatelessService}; +use crate::cloud_provider::service::{StatefulService, StatelessService}; +use crate::error::EngineError; use crate::unit_conversion::cpu_string_to_float; pub struct Environment { @@ -38,7 +39,7 @@ impl Environment { self.namespace.as_str() } - pub fn is_valid(&self) -> Result<(), ServiceError> { + pub fn is_valid(&self) -> Result<(), EngineError> { for service in self.stateful_services.iter() { match service.is_valid() { Err(err) => return Err(err), diff --git a/src/cloud_provider/gcp/mod.rs b/src/cloud_provider/gcp/mod.rs index a752a74a..8925d197 100644 --- a/src/cloud_provider/gcp/mod.rs +++ b/src/cloud_provider/gcp/mod.rs @@ -1,7 +1,7 @@ use std::any::Any; -use std::rc::Rc; -use crate::cloud_provider::{CloudProvider, CloudProviderError, Kind, TerraformStateCredentials}; +use crate::cloud_provider::{CloudProvider, Kind, TerraformStateCredentials}; +use crate::error::EngineError; use crate::models::{Context, Listener, ProgressListener}; pub struct GCP { @@ -43,7 +43,7 @@ impl<'x> CloudProvider for GCP { self.name.as_str() } - fn is_valid(&self) -> Result<(), CloudProviderError> { + fn is_valid(&self) -> Result<(), EngineError> { Ok(()) } diff --git a/src/cloud_provider/kubernetes.rs b/src/cloud_provider/kubernetes.rs index 4402ad3e..748f6e96 100644 --- a/src/cloud_provider/kubernetes.rs +++ b/src/cloud_provider/kubernetes.rs @@ -1,44 +1,54 @@ use std::any::Any; -use std::process::ExitStatus; -use std::rc::Rc; use serde::{Deserialize, Serialize}; -use serde_json::Value; use crate::cloud_provider::environment::Environment; -use crate::cloud_provider::service::ServiceError; use crate::cloud_provider::CloudProvider; -use crate::cmd::utilities::CmdError; use crate::dns_provider::DnsProvider; -use crate::models::{Context, Listener, Listeners, ProgressListener}; +use crate::error::{EngineError, EngineErrorCause, EngineErrorScope}; +use crate::models::{Context, Listener, Listeners}; pub trait Kubernetes { fn context(&self) -> &Context; fn kind(&self) -> Kind; fn id(&self) -> &str; fn name(&self) -> &str; + fn name_with_id(&self) -> String { + format!("{} ({})", self.name(), self.id()) + } fn version(&self) -> &str; fn region(&self) -> &str; fn cloud_provider(&self) -> &dyn CloudProvider; fn dns_provider(&self) -> &dyn DnsProvider; - fn is_valid(&self) -> Result<(), KubernetesError>; + fn is_valid(&self) -> Result<(), EngineError>; fn add_listener(&mut self, listener: Listener); fn listeners(&self) -> &Listeners; - fn resources(&self, environment: &Environment) -> Result; - fn on_create(&self) -> Result<(), KubernetesError>; - fn on_create_error(&self) -> Result<(), KubernetesError>; - fn on_upgrade(&self) -> Result<(), KubernetesError>; - fn on_upgrade_error(&self) -> Result<(), KubernetesError>; - fn on_downgrade(&self) -> Result<(), KubernetesError>; - fn on_downgrade_error(&self) -> Result<(), KubernetesError>; - fn on_delete(&self) -> Result<(), KubernetesError>; - fn on_delete_error(&self) -> Result<(), KubernetesError>; - fn deploy_environment(&self, environment: &Environment) -> Result<(), KubernetesError>; - fn deploy_environment_error(&self, environment: &Environment) -> Result<(), KubernetesError>; - fn pause_environment(&self, environment: &Environment) -> Result<(), KubernetesError>; - fn pause_environment_error(&self, environment: &Environment) -> Result<(), KubernetesError>; - fn delete_environment(&self, environment: &Environment) -> Result<(), KubernetesError>; - fn delete_environment_error(&self, environment: &Environment) -> Result<(), KubernetesError>; + fn resources(&self, environment: &Environment) -> Result; + fn on_create(&self) -> Result<(), EngineError>; + fn on_create_error(&self) -> Result<(), EngineError>; + fn on_upgrade(&self) -> Result<(), EngineError>; + fn on_upgrade_error(&self) -> Result<(), EngineError>; + fn on_downgrade(&self) -> Result<(), EngineError>; + fn on_downgrade_error(&self) -> Result<(), EngineError>; + fn on_delete(&self) -> Result<(), EngineError>; + fn on_delete_error(&self) -> Result<(), EngineError>; + fn deploy_environment(&self, environment: &Environment) -> Result<(), EngineError>; + fn deploy_environment_error(&self, environment: &Environment) -> Result<(), EngineError>; + fn pause_environment(&self, environment: &Environment) -> Result<(), EngineError>; + fn pause_environment_error(&self, environment: &Environment) -> Result<(), EngineError>; + fn delete_environment(&self, environment: &Environment) -> Result<(), EngineError>; + fn delete_environment_error(&self, environment: &Environment) -> Result<(), EngineError>; + fn engine_error_scope(&self) -> EngineErrorScope { + EngineErrorScope::Kubernetes(self.id().to_string(), self.name().to_string()) + } + fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError { + EngineError::new( + cause, + self.engine_error_scope(), + self.context().execution_id(), + Some(message), + ) + } } pub trait KubernetesNode { @@ -64,91 +74,59 @@ pub struct Resources { pub running_nodes: u16, } -#[derive(Debug)] -pub enum KubernetesError { - Cmd(CmdError), - Io(std::io::Error), - Create(ExitStatus), - Deploy(ServiceError), - Pause(ServiceError), - Delete(ServiceError), - Error, -} - -impl From for KubernetesError { - fn from(error: std::io::Error) -> Self { - KubernetesError::Io(error) - } -} - -impl From for KubernetesError { - fn from(error: CmdError) -> Self { - KubernetesError::Cmd(error) - } -} - -impl From for Option { - fn from(item: KubernetesError) -> Self { - return match item { - KubernetesError::Deploy(e) | KubernetesError::Pause(e) | KubernetesError::Delete(e) => { - Option::from(e) - } - _ => None, - }; - } -} - /// check that there is enough CPU and RAM, and pods resources /// before starting to deploy stateful and stateless services pub fn check_kubernetes_has_enough_resources_to_deploy_environment( kubernetes: &dyn Kubernetes, environment: &Environment, -) -> Result<(), KubernetesError> { +) -> Result<(), EngineError> { let resources = kubernetes.resources(environment)?; let required_resources = environment.required_resources(); + let cause = EngineErrorCause::User("Contact your Organization administrator and consider to \ + add one more node or upgrade your nodes configuration. If not possible, pause or delete unused environments"); + if required_resources.cpu > resources.free_cpu && required_resources.ram_in_mib > resources.free_ram_in_mib { // not enough cpu and ram to deploy environment - return Err(KubernetesError::Deploy(ServiceError::NotEnoughResources( - format!( - "There is not enough CPU and RAM resources on the Kubernetes '{}' cluster. \ + let message = format!( + "There is not enough CPU and RAM resources on the Kubernetes '{}' cluster. \ {} CPU and {}mib RAM requested. \ - {} CPU and {}mib RAM available. \ - Consider to add one more node or upgrade your nodes configuration.", - kubernetes.name(), - required_resources.cpu, - required_resources.ram_in_mib, - resources.free_cpu, - resources.free_ram_in_mib, - ), - ))); + {} CPU and {}mib RAM available.", + kubernetes.name(), + required_resources.cpu, + required_resources.ram_in_mib, + resources.free_cpu, + resources.free_ram_in_mib, + ); + + return Err(kubernetes.engine_error(cause, message)); } else if required_resources.cpu > resources.free_cpu { // not enough cpu to deploy environment - return Err(KubernetesError::Deploy(ServiceError::NotEnoughResources( - format!( - "There is not enough free CPU on the Kubernetes '{}' cluster. \ + let message = format!( + "There is not enough free CPU on the Kubernetes '{}' cluster. \ {} CPU requested. {} CPU available. \ Consider to add one more node or upgrade your nodes configuration.", - kubernetes.name(), - required_resources.cpu, - resources.free_cpu, - ), - ))); + kubernetes.name(), + required_resources.cpu, + resources.free_cpu, + ); + + return Err(kubernetes.engine_error(cause, message)); } else if required_resources.ram_in_mib > resources.free_ram_in_mib { // not enough ram to deploy environment - return Err(KubernetesError::Deploy(ServiceError::NotEnoughResources( - format!( - "There is not enough free RAM on the Kubernetes cluster '{}'. \ + let message = format!( + "There is not enough free RAM on the Kubernetes cluster '{}'. \ {}mib RAM requested. \ {}mib RAM available. \ Consider to add one more node or upgrade your nodes configuration.", - kubernetes.name(), - required_resources.ram_in_mib, - resources.free_ram_in_mib, - ), - ))); + kubernetes.name(), + required_resources.ram_in_mib, + resources.free_ram_in_mib, + ); + + return Err(kubernetes.engine_error(cause, message)); } let mut required_pods = environment.stateless_services.len() as u16; @@ -162,14 +140,14 @@ pub fn check_kubernetes_has_enough_resources_to_deploy_environment( if required_pods > resources.free_pods { // not enough free pods on the cluster - return Err(KubernetesError::Deploy(ServiceError::NotEnoughResources( - format!( - "There is not enough free Pods ({} required) on the Kubernetes cluster '{}'. \ + let message = format!( + "There is not enough free Pods ({} required) on the Kubernetes cluster '{}'. \ Consider to add one more node or upgrade your nodes configuration.", - required_pods, - kubernetes.name(), - ), - ))); + required_pods, + kubernetes.name(), + ); + + return Err(kubernetes.engine_error(cause, message)); } Ok(()) diff --git a/src/cloud_provider/mod.rs b/src/cloud_provider/mod.rs index 66d20c52..842d2091 100644 --- a/src/cloud_provider/mod.rs +++ b/src/cloud_provider/mod.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::cloud_provider::environment::Environment; use crate::cloud_provider::kubernetes::Kubernetes; +use crate::error::{EngineError, EngineErrorCause, EngineErrorScope}; use crate::models::{Context, Listener, ProgressListener}; pub mod aws; @@ -21,50 +22,26 @@ pub trait CloudProvider { fn id(&self) -> &str; fn organization_id(&self) -> &str; fn name(&self) -> &str; - fn is_valid(&self) -> Result<(), CloudProviderError>; + fn name_with_id(&self) -> String { + format!("{} ({})", self.name(), self.id()) + } + fn is_valid(&self) -> Result<(), EngineError>; fn add_listener(&mut self, listener: Listener); fn terraform_state_credentials(&self) -> &TerraformStateCredentials; + fn engine_error_scope(&self) -> EngineErrorScope { + EngineErrorScope::CloudProvider(self.id().to_string(), self.name().to_string()) + } + fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError { + EngineError::new( + cause, + self.engine_error_scope(), + self.context().execution_id(), + Some(message), + ) + } fn as_any(&self) -> &dyn Any; } -#[derive(Debug)] -pub enum CloudProviderError { - Credentials, - Error(Box), - Unknown, -} - -impl From> for CloudProviderError { - fn from(error: Box) -> Self { - CloudProviderError::Error(error) - } -} - -impl From> for CloudProviderError { - fn from(error: RusotoError) -> Self { - match error { - RusotoError::Credentials(_) => CloudProviderError::Credentials, - RusotoError::Service(_) => CloudProviderError::Unknown, - RusotoError::HttpDispatch(_) => CloudProviderError::Unknown, - RusotoError::Validation(_) => CloudProviderError::Unknown, - RusotoError::ParseError(_) => CloudProviderError::Unknown, - RusotoError::Unknown(e) => { - if e.status == 403 { - CloudProviderError::Credentials - } else { - CloudProviderError::Unknown - } - } - RusotoError::Blocking => CloudProviderError::Unknown, - } - } -} - -#[derive(Debug)] -pub enum DeployError { - Error, -} - #[derive(Serialize, Deserialize, Clone)] pub enum Kind { AWS, diff --git a/src/cloud_provider/service.rs b/src/cloud_provider/service.rs index c27c5337..021b6a46 100644 --- a/src/cloud_provider/service.rs +++ b/src/cloud_provider/service.rs @@ -1,4 +1,3 @@ -use std::io::Error; use std::net::TcpStream; use std::process::id; @@ -8,15 +7,17 @@ use crate::build_platform::Image; use crate::cloud_provider::environment::Environment; use crate::cloud_provider::kubernetes::Kubernetes; use crate::cloud_provider::DeploymentTarget; -use crate::cmd::utilities::CmdError; +use crate::error::{EngineError, EngineErrorCause, EngineErrorScope}; use crate::models::{Context, ProgressScope}; -use crate::transaction::CommitError; pub trait Service { fn context(&self) -> &Context; fn service_type(&self) -> ServiceType; fn id(&self) -> &str; fn name(&self) -> &str; + fn name_with_id(&self) -> String { + format!("{} ({})", self.name(), self.id()) + } fn version(&self) -> &str; fn action(&self) -> &Action; fn private_port(&self) -> Option; @@ -35,13 +36,21 @@ pub trait Service { } } - fn is_valid(&self) -> Result<(), ServiceError> { + fn is_valid(&self) -> Result<(), EngineError> { let binaries = ["kubectl", "helm", "terraform", "aws-iam-authenticator"]; for binary in binaries.iter() { if !crate::cmd::utilities::does_binary_exist(binary) { let err = format!("{} binary not found", binary); - return Err(ServiceError::Unexpected(err)); + + let cause = EngineErrorCause::Internal; + + return Err(EngineError::new( + EngineErrorCause::Internal, + EngineErrorScope::Engine, + self.id(), + Some(err), + )); } } @@ -93,7 +102,7 @@ pub trait Service { } pub trait StatelessService: Service + Create + Pause + Delete { - fn exec_action(&self, deployment_target: &DeploymentTarget) -> Result<(), ServiceError> { + fn exec_action(&self, deployment_target: &DeploymentTarget) -> Result<(), EngineError> { match self.action() { crate::cloud_provider::service::Action::Create => self.on_create(deployment_target), crate::cloud_provider::service::Action::Delete => self.on_delete(deployment_target), @@ -106,7 +115,7 @@ pub trait StatelessService: Service + Create + Pause + Delete { pub trait StatefulService: Service + Create + Pause + Delete + Backup + Clone + Upgrade + Downgrade { - fn exec_action(&self, deployment_target: &DeploymentTarget) -> Result<(), ServiceError> { + fn exec_action(&self, deployment_target: &DeploymentTarget) -> Result<(), EngineError> { match self.action() { crate::cloud_provider::service::Action::Create => self.on_create(deployment_target), crate::cloud_provider::service::Action::Delete => self.on_delete(deployment_target), @@ -119,59 +128,109 @@ pub trait StatefulService: pub trait Application: StatelessService { fn image(&self) -> &Image; fn set_image(&mut self, image: Image); + fn engine_error_scope(&self) -> EngineErrorScope { + EngineErrorScope::Application(self.id().to_string(), self.name().to_string()) + } + fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError { + EngineError::new( + cause, + self.engine_error_scope(), + self.context().execution_id(), + Some(message), + ) + } } -pub trait ExternalService: Application {} +pub trait ExternalService: StatelessService { + fn engine_error_scope(&self) -> EngineErrorScope { + EngineErrorScope::ExternalService(self.id().to_string(), self.name().to_string()) + } + fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError { + EngineError::new( + cause, + self.engine_error_scope(), + self.context().execution_id(), + Some(message), + ) + } +} pub trait Router: StatelessService { - fn check_domains(&self) -> Result<(), ServiceError>; + fn check_domains(&self) -> Result<(), EngineError>; + fn engine_error_scope(&self) -> EngineErrorScope { + EngineErrorScope::Router(self.id().to_string(), self.name().to_string()) + } + fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError { + EngineError::new( + cause, + self.engine_error_scope(), + self.context().execution_id(), + Some(message), + ) + } } -pub trait Database: StatefulService {} +pub trait Database: StatefulService { + fn engine_error_scope(&self) -> EngineErrorScope { + EngineErrorScope::Database( + self.id().to_string(), + self.service_type().name().to_string(), + self.name().to_string(), + ) + } + fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError { + EngineError::new( + cause, + self.engine_error_scope(), + self.context().execution_id(), + Some(message), + ) + } +} pub trait Create { - fn on_create(&self, target: &DeploymentTarget) -> Result<(), ServiceError>; - fn on_create_check(&self) -> Result<(), ServiceError>; - fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError>; + fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError>; + fn on_create_check(&self) -> Result<(), EngineError>; + fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError>; } pub trait Pause { - fn on_pause(&self, target: &DeploymentTarget) -> Result<(), ServiceError>; - fn on_pause_check(&self) -> Result<(), ServiceError>; - fn on_pause_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError>; + fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError>; + fn on_pause_check(&self) -> Result<(), EngineError>; + fn on_pause_error(&self, target: &DeploymentTarget) -> Result<(), EngineError>; } pub trait Delete { - fn on_delete(&self, target: &DeploymentTarget) -> Result<(), ServiceError>; - fn on_delete_check(&self) -> Result<(), ServiceError>; - fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError>; + fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError>; + fn on_delete_check(&self) -> Result<(), EngineError>; + fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), EngineError>; } pub trait Backup { - fn on_backup(&self, target: &DeploymentTarget) -> Result<(), ServiceError>; - fn on_backup_check(&self) -> Result<(), ServiceError>; - fn on_backup_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError>; - fn on_restore(&self, target: &DeploymentTarget) -> Result<(), ServiceError>; - fn on_restore_check(&self) -> Result<(), ServiceError>; - fn on_restore_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError>; + fn on_backup(&self, target: &DeploymentTarget) -> Result<(), EngineError>; + fn on_backup_check(&self) -> Result<(), EngineError>; + fn on_backup_error(&self, target: &DeploymentTarget) -> Result<(), EngineError>; + fn on_restore(&self, target: &DeploymentTarget) -> Result<(), EngineError>; + fn on_restore_check(&self) -> Result<(), EngineError>; + fn on_restore_error(&self, target: &DeploymentTarget) -> Result<(), EngineError>; } pub trait Clone { - fn on_clone(&self, target: &DeploymentTarget) -> Result<(), ServiceError>; - fn on_clone_check(&self) -> Result<(), ServiceError>; - fn on_clone_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError>; + fn on_clone(&self, target: &DeploymentTarget) -> Result<(), EngineError>; + fn on_clone_check(&self) -> Result<(), EngineError>; + fn on_clone_error(&self, target: &DeploymentTarget) -> Result<(), EngineError>; } pub trait Upgrade { - fn on_upgrade(&self, target: &DeploymentTarget) -> Result<(), ServiceError>; - fn on_upgrade_check(&self) -> Result<(), ServiceError>; - fn on_upgrade_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError>; + fn on_upgrade(&self, target: &DeploymentTarget) -> Result<(), EngineError>; + fn on_upgrade_check(&self) -> Result<(), EngineError>; + fn on_upgrade_error(&self, target: &DeploymentTarget) -> Result<(), EngineError>; } pub trait Downgrade { - fn on_downgrade(&self, target: &DeploymentTarget) -> Result<(), ServiceError>; - fn on_downgrade_check(&self) -> Result<(), ServiceError>; - fn on_downgrade_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError>; + fn on_downgrade(&self, target: &DeploymentTarget) -> Result<(), EngineError>; + fn on_downgrade_check(&self) -> Result<(), EngineError>; + fn on_downgrade_error(&self, target: &DeploymentTarget) -> Result<(), EngineError>; } #[derive(Clone, Eq, PartialEq, Hash)] @@ -212,44 +271,12 @@ impl<'a> ServiceType<'a> { match self { ServiceType::Application => "Application", ServiceType::ExternalService => "ExternalService", - ServiceType::Database(_) => "Database", + ServiceType::Database(db_type) => match db_type { + DatabaseType::PostgreSQL(_) => "PostgreSQL database", + DatabaseType::MongoDB(_) => "MongoDB database", + DatabaseType::MySQL(_) => "MySQL database", + }, ServiceType::Router => "Router", } } } - -#[derive(Debug)] -pub enum ServiceError { - OnCreateFailed, - CheckFailed, - Cmd(CmdError), - Io(Error), - NotEnoughResources(String), - Unexpected(String), -} - -impl From for ServiceError { - fn from(err: Error) -> Self { - ServiceError::Io(err) - } -} - -impl From for ServiceError { - fn from(err: CmdError) -> Self { - ServiceError::Cmd(err) - } -} - -impl From for Option { - fn from(err: CommitError) -> Self { - return match err { - CommitError::DeleteEnvironment(e) - | CommitError::PauseEnvironment(e) - | CommitError::DeployEnvironment(e) - | CommitError::DeleteKubernetes(e) - | CommitError::CreateKubernetes(e) => Option::from(e), - CommitError::NotValidService(e) => Option::Some(e), - _ => None, - }; - } -} diff --git a/src/cmd/helm.rs b/src/cmd/helm.rs index 72bd4739..17f5e5ef 100644 --- a/src/cmd/helm.rs +++ b/src/cmd/helm.rs @@ -12,8 +12,9 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use crate::cmd::structs::{Helm, HelmHistoryRow}; -use crate::cmd::utilities::{exec_with_envs_and_output, CmdError}; +use crate::cmd::utilities::exec_with_envs_and_output; use crate::constants::{KUBECONFIG, TF_PLUGIN_CACHE_DIR}; +use crate::error::{SimpleError, SimpleErrorKind}; pub fn helm_exec_with_upgrade_history

( kubernetes_config: P, @@ -21,7 +22,7 @@ pub fn helm_exec_with_upgrade_history

( release_name: &str, chart_root_dir: P, envs: Vec<(&str, &str)>, -) -> Result, CmdError> +) -> Result, SimpleError> where P: AsRef, { @@ -63,7 +64,7 @@ pub fn helm_exec_upgrade

( release_name: &str, chart_root_dir: P, envs: Vec<(&str, &str)>, -) -> Result<(), CmdError> +) -> Result<(), SimpleError> where P: AsRef, { @@ -99,7 +100,7 @@ pub fn helm_exec_uninstall

( namespace: &str, release_name: &str, envs: Vec<(&str, &str)>, -) -> Result<(), CmdError> +) -> Result<(), SimpleError> where P: AsRef, { @@ -129,7 +130,7 @@ pub fn helm_exec_history

( namespace: &str, release_name: &str, envs: Vec<(&str, &str)>, -) -> Result, CmdError> +) -> Result, SimpleError> where P: AsRef, { @@ -178,7 +179,7 @@ pub fn helm_uninstall_list

( kubernetes_config: P, helmlist: Vec, envs: Vec<(&str, &str)>, -) -> Result +) -> Result where P: AsRef, { @@ -217,7 +218,7 @@ pub fn helm_exec_upgrade_with_override_file

( chart_root_dir: P, override_file: &str, envs: Vec<(&str, &str)>, -) -> Result<(), CmdError> +) -> Result<(), SimpleError> where P: AsRef, { @@ -259,7 +260,7 @@ pub fn helm_exec_with_upgrade_history_with_override

( chart_root_dir: P, override_file: &str, envs: Vec<(&str, &str)>, -) -> Result, CmdError> +) -> Result, SimpleError> where P: AsRef, { @@ -296,7 +297,10 @@ where }) } -pub fn helm_list

(kubernetes_config: P, envs: Vec<(&str, &str)>) -> Result, CmdError> +pub fn helm_list

( + kubernetes_config: P, + envs: Vec<(&str, &str)>, +) -> Result, SimpleError> where P: AsRef, { @@ -330,14 +334,15 @@ where } } Err(e) => { - error!("Error while deserializing all helms names {}", e); - return Err(CmdError::Io(Error::new(std::io::ErrorKind::InvalidData, e))); + let message = format!("Error while deserializing all helms names {}", e); + error!("{}", message.as_str()); + return Err(SimpleError::new(SimpleErrorKind::Other, Some(message))); } } Ok(helms_name) } -pub fn helm_exec(args: Vec<&str>, envs: Vec<(&str, &str)>) -> Result<(), CmdError> { +pub fn helm_exec(args: Vec<&str>, envs: Vec<(&str, &str)>) -> Result<(), SimpleError> { helm_exec_with_output( args, envs, @@ -355,7 +360,7 @@ pub fn helm_exec_with_output( envs: Vec<(&str, &str)>, stdout_output: F, stderr_output: X, -) -> Result<(), CmdError> +) -> Result<(), SimpleError> where F: FnMut(Result), X: FnMut(Result), @@ -373,7 +378,7 @@ pub fn kubectl_exec_with_output( envs: Vec<(&str, &str)>, stdout_output: F, stderr_output: X, -) -> Result<(), CmdError> +) -> Result<(), SimpleError> where F: FnMut(Result), X: FnMut(Result), diff --git a/src/cmd/kubectl.rs b/src/cmd/kubectl.rs index 91483e9c..068be7b8 100644 --- a/src/cmd/kubectl.rs +++ b/src/cmd/kubectl.rs @@ -15,15 +15,16 @@ use crate::cmd::structs::{ Item, KubernetesJob, KubernetesList, KubernetesNode, KubernetesPod, KubernetesPodStatusPhase, KubernetesService, }; -use crate::cmd::utilities::{exec_with_envs_and_output, CmdError}; +use crate::cmd::utilities::exec_with_envs_and_output; use crate::constants::{KUBECONFIG, TF_PLUGIN_CACHE_DIR}; +use crate::error::{SimpleError, SimpleErrorKind}; pub fn kubectl_exec_with_output( args: Vec<&str>, envs: Vec<(&str, &str)>, stdout_output: F, stderr_output: X, -) -> Result<(), CmdError> +) -> Result<(), SimpleError> where F: FnMut(Result), X: FnMut(Result), @@ -41,7 +42,7 @@ pub fn kubectl_exec_get_external_ingress_hostname

( namespace: &str, selector: &str, envs: Vec<(&str, &str)>, -) -> Result, CmdError> +) -> Result, SimpleError> where P: AsRef, { @@ -74,10 +75,10 @@ where Err(err) => { error!("{:?}", err); error!("{}", output_string.as_str()); - return Err(CmdError::Io(Error::new( - std::io::ErrorKind::InvalidData, - output_string, - ))); + return Err(SimpleError::new( + SimpleErrorKind::Other, + Some(output_string), + )); } }; @@ -115,7 +116,7 @@ pub fn kubectl_exec_is_pod_ready_with_retry

( namespace: &str, selector: &str, envs: Vec<(&str, &str)>, -) -> Result, CmdError> +) -> Result, SimpleError> where P: AsRef, { @@ -148,7 +149,7 @@ where total_delay: _, tries: _, } => Ok(Some(false)), - retry::Error::Internal(err) => Err(CmdError::Unexpected(err)), + retry::Error::Internal(err) => Err(SimpleError::new(SimpleErrorKind::Other, Some(err))), }, Ok(_) => Ok(Some(true)), } @@ -159,7 +160,7 @@ pub fn kubectl_exec_is_pod_ready

( namespace: &str, selector: &str, envs: Vec<(&str, &str)>, -) -> Result, CmdError> +) -> Result, SimpleError> where P: AsRef, { @@ -189,10 +190,10 @@ where Err(err) => { error!("{:?}", err); error!("{}", output_string.as_str()); - return Err(CmdError::Io(Error::new( - std::io::ErrorKind::InvalidData, - output_string, - ))); + return Err(SimpleError::new( + SimpleErrorKind::Other, + Some(output_string), + )); } }; @@ -223,7 +224,7 @@ pub fn kubectl_exec_is_job_ready_with_retry

( namespace: &str, job_name: &str, envs: Vec<(&str, &str)>, -) -> Result, CmdError> +) -> Result, SimpleError> where P: AsRef, { @@ -256,7 +257,7 @@ where total_delay: _, tries: _, } => Ok(Some(false)), - retry::Error::Internal(err) => Err(CmdError::Unexpected(err)), + retry::Error::Internal(err) => Err(SimpleError::new(SimpleErrorKind::Other, Some(err))), }, Ok(_) => Ok(Some(true)), } @@ -267,7 +268,7 @@ pub fn kubectl_exec_is_job_ready

( namespace: &str, job_name: &str, envs: Vec<(&str, &str)>, -) -> Result, CmdError> +) -> Result, SimpleError> where P: AsRef, { @@ -296,10 +297,10 @@ where Err(err) => { error!("{:?}", err); error!("{}", output_string.as_str()); - return Err(CmdError::Io(Error::new( - std::io::ErrorKind::InvalidData, - output_string, - ))); + return Err(SimpleError::new( + SimpleErrorKind::Other, + Some(output_string), + )); } }; @@ -314,7 +315,7 @@ pub fn kubectl_exec_create_namespace

( kubernetes_config: P, namespace: &str, envs: Vec<(&str, &str)>, -) -> Result<(), CmdError> +) -> Result<(), SimpleError> where P: AsRef, { @@ -344,7 +345,7 @@ pub fn create_sample_secret_terraform_in_namespace

( kubernetes_config: P, namespace_to_override: &str, envs: &Vec<(&str, &str)>, -) -> Result +) -> Result where P: AsRef, { @@ -378,7 +379,7 @@ pub fn does_contain_terraform_tfstate

( kubernetes_config: P, namespace: &str, envs: &Vec<(&str, &str)>, -) -> Result +) -> Result where P: AsRef, { @@ -409,7 +410,7 @@ where pub fn kubectl_exec_get_all_namespaces

( kubernetes_config: P, envs: Vec<(&str, &str)>, -) -> Result, Error> +) -> Result, SimpleError> where P: AsRef, { @@ -441,11 +442,15 @@ where } } Err(e) => { - error!("While deserializing Kubernetes namespaces names {}", e); - return Err(Error::from(CmdError::Io(Error::new( - std::io::ErrorKind::InvalidData, - output_string, - )))); + error!( + "Error while deserializing Kubernetes namespaces names {}", + e + ); + + return Err(SimpleError::new( + SimpleErrorKind::Other, + Some(output_string), + )); } }; Ok(to_return) @@ -455,17 +460,17 @@ pub fn kubectl_exec_delete_namespace

( kubernetes_config: P, namespace: &str, envs: Vec<(&str, &str)>, -) -> Result<(), CmdError> +) -> Result<(), SimpleError> where P: AsRef, { match does_contain_terraform_tfstate(&kubernetes_config, &namespace, &envs) { Ok(exist) => match exist { true => { - return Err(CmdError::Io(Error::new( - std::io::ErrorKind::Other, - "Namespace contains terraform tfstates in secret, can't delete it !", - ))); + return Err(SimpleError::new( + SimpleErrorKind::Other, + Some("Namespace contains terraform tfstates in secret, can't delete it !"), + )); } false => info!( "Namespace {} doesn't contain any tfstates, able to delete it", @@ -474,7 +479,7 @@ where }, Err(e) => warn!( "Unable to execute describe on secrets: {}. it may not exist anymore?", - e + e.message.unwrap_or("".into()) ), }; @@ -502,7 +507,7 @@ pub fn kubectl_exec_delete_secret

( kubernetes_config: P, secret: &str, envs: Vec<(&str, &str)>, -) -> Result<(), CmdError> +) -> Result<(), SimpleError> where P: AsRef, { @@ -531,7 +536,7 @@ pub fn kubectl_exec_logs

( namespace: &str, selector: &str, envs: Vec<(&str, &str)>, -) -> Result +) -> Result where P: AsRef, { @@ -561,7 +566,7 @@ pub fn kubectl_exec_describe_pod

( namespace: &str, selector: &str, envs: Vec<(&str, &str)>, -) -> Result +) -> Result where P: AsRef, { @@ -589,7 +594,7 @@ where pub fn kubectl_exec_get_node

( kubernetes_config: P, envs: Vec<(&str, &str)>, -) -> Result, CmdError> +) -> Result, SimpleError> where P: AsRef, { @@ -619,10 +624,10 @@ where Err(err) => { error!("{:?}", err); error!("{}", output_string.as_str()); - return Err(CmdError::Io(Error::new( - std::io::ErrorKind::InvalidData, - output_string, - ))); + return Err(SimpleError::new( + SimpleErrorKind::Other, + Some(output_string), + )); } }; diff --git a/src/cmd/terraform.rs b/src/cmd/terraform.rs index 5ab727fd..587c99e1 100644 --- a/src/cmd/terraform.rs +++ b/src/cmd/terraform.rs @@ -11,13 +11,14 @@ use retry::OperationResult; use serde::{Deserialize, Serialize}; use serde_json::Value; -use crate::cmd::utilities::{exec_with_envs_and_output, CmdError}; +use crate::cmd::utilities::exec_with_envs_and_output; use crate::constants::{KUBECONFIG, TF_PLUGIN_CACHE_DIR}; +use crate::error::SimpleError; fn terraform_exec_with_init_validate( root_dir: &str, first_time_init_terraform: bool, -) -> Result<(), CmdError> { +) -> Result<(), SimpleError> { // terraform init let init_args = if first_time_init_terraform { vec!["init"] @@ -37,7 +38,7 @@ fn terraform_exec_with_init_validate( fn terraform_exec_with_init_validate_plan( root_dir: &str, first_time_init_terraform: bool, -) -> Result<(), CmdError> { +) -> Result<(), SimpleError> { // terraform init let init_args = if first_time_init_terraform { vec!["init"] @@ -60,7 +61,7 @@ fn terraform_exec_with_init_validate_plan( pub fn terraform_exec_with_init_validate_plan_apply( root_dir: &str, first_time_init_terraform: bool, -) -> Result<(), CmdError> { +) -> Result<(), SimpleError> { // terraform init and plan terraform_exec_with_init_validate_plan(root_dir, first_time_init_terraform); @@ -70,7 +71,7 @@ pub fn terraform_exec_with_init_validate_plan_apply( Ok(()) } -pub fn terraform_exec_with_init_validate_destroy(root_dir: &str) -> Result<(), CmdError> { +pub fn terraform_exec_with_init_validate_destroy(root_dir: &str) -> Result<(), SimpleError> { // terraform init and plan terraform_exec_with_init_validate(root_dir, false); @@ -78,11 +79,11 @@ pub fn terraform_exec_with_init_validate_destroy(root_dir: &str) -> Result<(), C terraform_exec(root_dir, vec!["destroy", "-auto-approve"]) } -pub fn terraform_exec(root_dir: &str, args: Vec<&str>) -> Result<(), CmdError> { +pub fn terraform_exec(root_dir: &str, args: Vec<&str>) -> Result<(), SimpleError> { let home_dir = home_dir().expect("Could not find $HOME"); let tf_plugin_cache_dir = format!("{}/.terraform.d/plugin-cache", home_dir.to_str().unwrap()); - match exec_with_envs_and_output( + exec_with_envs_and_output( format!("{} terraform", root_dir).as_str(), args, vec![(TF_PLUGIN_CACHE_DIR, tf_plugin_cache_dir.as_str())], @@ -92,10 +93,5 @@ pub fn terraform_exec(root_dir: &str, args: Vec<&str>) -> Result<(), CmdError> { |line: Result| { error!("{}", line.unwrap()); }, - ) { - Err(err) => return Err(err), - Ok(out) => Ok(out), - }; - - Ok(()) + ) } diff --git a/src/cmd/utilities.rs b/src/cmd/utilities.rs index 8195d463..7d886df1 100644 --- a/src/cmd/utilities.rs +++ b/src/cmd/utilities.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use crate::constants::{KUBECONFIG, TF_PLUGIN_CACHE_DIR}; +use crate::error::{SimpleError, SimpleErrorKind}; fn command

(binary: P, args: Vec<&str>, envs: Option>) -> Command where @@ -53,7 +54,7 @@ where cmd } -pub fn exec

(binary: P, args: Vec<&str>) -> Result<(), CmdError> +pub fn exec

(binary: P, args: Vec<&str>) -> Result<(), SimpleError> where P: AsRef, { @@ -62,21 +63,24 @@ where let exit_status = match command(binary, args, None).spawn().unwrap().wait() { Ok(x) => x, - Err(err) => return Err(CmdError::Io(err)), + Err(err) => return Err(SimpleError::from(err)), }; if exit_status.success() { return Ok(()); } - Err(CmdError::Exec(exit_status)) + Err(SimpleError::new::<&str>( + SimpleErrorKind::Command(exit_status), + None, + )) } pub fn exec_with_envs

( binary: P, args: Vec<&str>, envs: Vec<(&str, &str)>, -) -> Result<(), CmdError> +) -> Result<(), SimpleError> where P: AsRef, { @@ -85,14 +89,17 @@ where let exit_status = match command(binary, args, Some(envs)).spawn().unwrap().wait() { Ok(x) => x, - Err(err) => return Err(CmdError::Io(err)), + Err(err) => return Err(SimpleError::from(err)), }; if exit_status.success() { return Ok(()); } - Err(CmdError::Exec(exit_status)) + Err(SimpleError::new::( + SimpleErrorKind::Command(exit_status), + None, + )) } fn _with_output(mut child: Child, mut stdout_output: F, mut stderr_output: X) -> Child @@ -118,7 +125,7 @@ pub fn exec_with_output( args: Vec<&str>, stdout_output: F, stderr_output: X, -) -> Result<(), CmdError> +) -> Result<(), SimpleError> where P: AsRef, F: FnMut(Result), @@ -135,14 +142,17 @@ where let exit_status = match child.wait() { Ok(x) => x, - Err(err) => return Err(CmdError::Io(err)), + Err(err) => return Err(SimpleError::from(err)), }; if exit_status.success() { return Ok(()); } - Err(CmdError::Exec(exit_status)) + Err(SimpleError::new( + SimpleErrorKind::Command(exit_status), + Some(command_string), + )) } pub fn exec_with_envs_and_output( @@ -151,7 +161,7 @@ pub fn exec_with_envs_and_output( envs: Vec<(&str, &str)>, stdout_output: F, stderr_output: X, -) -> Result<(), CmdError> +) -> Result<(), SimpleError> where P: AsRef, F: FnMut(Result), @@ -168,14 +178,17 @@ where let exit_status = match child.wait() { Ok(x) => x, - Err(err) => return Err(CmdError::Io(err)), + Err(err) => return Err(SimpleError::from(err)), }; if exit_status.success() { return Ok(()); } - Err(CmdError::Exec(exit_status)) + Err(SimpleError::new( + SimpleErrorKind::Command(exit_status), + Some(command_string), + )) } // return the output of "binary_name" --version @@ -238,35 +251,3 @@ where args.join(" ") ) } - -#[derive(Debug)] -pub enum CmdError { - Exec(ExitStatus), - Io(Error), - Unexpected(String), -} - -impl Display for CmdError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let s = match self { - CmdError::Exec(status) => format!("CmdError: Exec({})", status), - CmdError::Io(io) => format!("CmdError: IO: {}", io), - CmdError::Unexpected(s) => format!("CmdError: Unexpected: {}", s), - }; - write!(f, "{}", s) - } -} - -impl std::error::Error for CmdError {} - -impl From for CmdError { - fn from(err: Error) -> Self { - CmdError::Io(err) - } -} - -impl From for std::io::Error { - fn from(e: CmdError) -> Self { - std::io::Error::new(std::io::ErrorKind::Other, e) - } -} diff --git a/src/container_registry/docker_hub.rs b/src/container_registry/docker_hub.rs index 409f2dbf..9b71bfc4 100644 --- a/src/container_registry/docker_hub.rs +++ b/src/container_registry/docker_hub.rs @@ -2,10 +2,8 @@ use std::rc::Rc; use crate::build_platform::Image; use crate::cmd; -use crate::cmd::utilities::CmdError; -use crate::container_registry::{ - ContainerRegistry, ContainerRegistryError, Kind, PushError, PushResult, -}; +use crate::container_registry::{ContainerRegistry, EngineError, Kind, PushResult}; +use crate::error::EngineErrorCause; use crate::models::{Context, Listener, Listeners, ProgressListener}; pub struct DockerHub { @@ -47,7 +45,7 @@ impl ContainerRegistry for DockerHub { self.name.as_str() } - fn is_valid(&self) -> Result<(), ContainerRegistryError> { + fn is_valid(&self) -> Result<(), EngineError> { // check the version of docker and print it as info let mut output_from_cmd = String::new(); cmd::utilities::exec_with_output( @@ -70,19 +68,19 @@ impl ContainerRegistry for DockerHub { self.listeners.push(listener); } - fn on_create(&self) -> Result<(), ContainerRegistryError> { + fn on_create(&self) -> Result<(), EngineError> { Ok(()) } - fn on_create_error(&self) -> Result<(), ContainerRegistryError> { + fn on_create_error(&self) -> Result<(), EngineError> { Ok(()) } - fn on_delete(&self) -> Result<(), ContainerRegistryError> { + fn on_delete(&self) -> Result<(), EngineError> { Ok(()) } - fn on_delete_error(&self) -> Result<(), ContainerRegistryError> { + fn on_delete_error(&self) -> Result<(), EngineError> { Ok(()) } @@ -104,20 +102,13 @@ impl ContainerRegistry for DockerHub { ], envs.clone(), ) { - Err(err) => match err { - CmdError::Exec(exit_status) => { - error!("Cannot login into dockerhub"); - return false; - } - CmdError::Io(err) => { - error!("IO error on dockerhub login: {}", err); - return false; - } - CmdError::Unexpected(err) => { - error!("Unexpected error on dockerhub login: {}", err); - return false; - } - }, + Err(err) => { + if let Some(message) = err.message { + error!("{}", message); + }; + + return false; + } _ => {} }; @@ -130,23 +121,24 @@ impl ContainerRegistry for DockerHub { let mut exist_stdoud: bool = false; let mut exist_stderr: bool = true; + // TODO Change this by using curl lib cmd::utilities::exec_with_envs_and_output( "curl", vec!["--silent", "-f", "-lSL", &curl_path], envs.clone(), |r_out| match r_out { - Ok(s) => exist_stdoud = true, + Ok(_) => exist_stdoud = true, Err(e) => error!("Error while getting stdout from curl {}", e), }, |r_err| match r_err { - Ok(s) => exist_stderr = true, + Ok(_) => exist_stderr = true, Err(e) => error!("Error while getting stderr from curl {}", e), }, ); exist_stdoud } - fn push(&self, image: &Image, force_push: bool) -> Result { + fn push(&self, image: &Image, force_push: bool) -> Result { let envs = match self.context.docker_tcp_socket() { Some(tcp_socket) => vec![("DOCKER_HOST", tcp_socket.as_str())], None => vec![], @@ -163,11 +155,14 @@ impl ContainerRegistry for DockerHub { ], envs.clone(), ) { - Err(err) => match err { - CmdError::Exec(exit_status) => return Err(PushError::CredentialsError), - CmdError::Io(err) => return Err(PushError::IoError(err)), - CmdError::Unexpected(err) => return Err(PushError::Unknown(err)), - }, + Err(_) => { + return Err( + self.engine_error( + EngineErrorCause::User("Your DockerHub account seems to be no longer valid (bad Credentials). \ + Please contact your Organization administrator to fix or change the Credentials."), + format!("failed to login to DockerHub {}", self.name_with_id())) + ); + } _ => {} }; @@ -181,20 +176,30 @@ impl ContainerRegistry for DockerHub { ], envs.clone(), ) { - Err(err) => match err { - CmdError::Exec(exit_status) => return Err(PushError::ImageTagFailed), - CmdError::Io(err) => return Err(PushError::IoError(err)), - CmdError::Unexpected(err) => return Err(PushError::Unknown(err)), - }, + Err(_) => { + return Err(self.engine_error( + EngineErrorCause::Internal, + format!( + "failed to tag image ({}) {:?}", + image.name_with_tag(), + image, + ), + )); + } _ => {} }; match cmd::utilities::exec_with_envs("docker", vec!["push", dest.as_str()], envs) { - Err(err) => match err { - CmdError::Exec(exit_status) => return Err(PushError::ImagePushFailed), - CmdError::Io(err) => return Err(PushError::IoError(err)), - CmdError::Unexpected(err) => return Err(PushError::Unknown(err)), - }, + Err(_) => { + return Err(self.engine_error( + EngineErrorCause::Internal, + format!( + "failed to push image {:?} into DockerHub {}", + image, + self.name_with_id(), + ), + )); + } _ => {} }; @@ -204,7 +209,7 @@ impl ContainerRegistry for DockerHub { Ok(PushResult { image }) } - fn push_error(&self, _image: &Image) -> Result { + fn push_error(&self, _image: &Image) -> Result { unimplemented!() } } diff --git a/src/container_registry/docr.rs b/src/container_registry/docr.rs index b93a4225..e96e1a7c 100644 --- a/src/container_registry/docr.rs +++ b/src/container_registry/docr.rs @@ -6,10 +6,8 @@ use digitalocean::DigitalOcean; use crate::build_platform::Image; use crate::cmd; -use crate::cmd::utilities::CmdError; -use crate::container_registry::{ - ContainerRegistry, ContainerRegistryError, Kind, PushError, PushResult, -}; +use crate::container_registry::{ContainerRegistry, EngineError, Kind, PushResult}; +use crate::error::{EngineErrorCause, EngineErrorScope}; use crate::models::{Context, Listener, ProgressListener}; // TODO : use --output json @@ -33,7 +31,7 @@ impl DOCR { DigitalOcean::new(self.api_key.as_str()).unwrap() } - pub fn create_repository(&self, _image: &Image) -> Result<(), ContainerRegistryError> { + pub fn create_repository(&self, _image: &Image) -> Result<(), EngineError> { match cmd::utilities::exec( "doctl", vec![ @@ -44,35 +42,47 @@ impl DOCR { self.api_key.as_str(), ], ) { - Err(err) => match err { - CmdError::Exec(_exit_status) => return Err(ContainerRegistryError::Unknown), - CmdError::Io(err) => return Err(ContainerRegistryError::Unknown), - CmdError::Unexpected(err) => return Err(ContainerRegistryError::Unknown), - }, + Err(_) => { + return Err(self.engine_error( + EngineErrorCause::Internal, + format!("failed to create DOCR {}", self.registry_name.as_str()), + )); + } _ => {} }; + Ok(()) } - pub fn push_image(&self, dest: String, image: &Image) -> Result { + pub fn push_image(&self, dest: String, image: &Image) -> Result { match cmd::utilities::exec( "docker", vec!["tag", image.name_with_tag().as_str(), dest.as_str()], ) { - Err(err) => match err { - CmdError::Exec(_exit_status) => return Err(PushError::ImageTagFailed), - CmdError::Io(err) => return Err(PushError::IoError(err)), - CmdError::Unexpected(err) => return Err(PushError::Unknown(err)), - }, + Err(_) => { + return Err(self.engine_error( + EngineErrorCause::Internal, + format!( + "failed to tag image ({}) {:?}", + image.name_with_tag(), + image, + ), + )); + } _ => {} }; match cmd::utilities::exec("docker", vec!["push", dest.as_str()]) { - Err(err) => match err { - CmdError::Exec(_exit_status) => return Err(PushError::ImagePushFailed), - CmdError::Io(err) => return Err(PushError::IoError(err)), - CmdError::Unexpected(err) => return Err(PushError::Unknown(err)), - }, + Err(_) => { + return Err(self.engine_error( + EngineErrorCause::Internal, + format!( + "failed to push image {:?} into DOCR {}", + image, + self.name_with_id(), + ), + )); + } _ => {} }; @@ -82,12 +92,12 @@ impl DOCR { Ok(PushResult { image }) } - fn get_or_create_repository(&self, _image: &Image) -> Result<(), ContainerRegistryError> { + fn get_or_create_repository(&self, _image: &Image) -> Result<(), EngineError> { // TODO check if repository really exist self.create_repository(&_image) } - fn delete_repository(&self, _image: &Image) -> Result<(), ContainerRegistryError> { + fn delete_repository(&self, _image: &Image) -> Result<(), EngineError> { match cmd::utilities::exec( "doctl", vec![ @@ -99,11 +109,16 @@ impl DOCR { self.api_key.as_str(), ], ) { - Err(err) => match err { - CmdError::Exec(exit_status) => return Err(ContainerRegistryError::Unknown), - CmdError::Io(err) => return Err(ContainerRegistryError::Unknown), - CmdError::Unexpected(err) => return Err(ContainerRegistryError::Unknown), - }, + Err(_) => { + return Err(self.engine_error( + EngineErrorCause::Internal, + format!( + "failed to delete DOCR repository {} from {}", + self.registry_name.as_str(), + self.name_with_id(), + ), + )); + } _ => {} }; Ok(()) @@ -127,7 +142,7 @@ impl ContainerRegistry for DOCR { unimplemented!() } - fn is_valid(&self) -> Result<(), ContainerRegistryError> { + fn is_valid(&self) -> Result<(), EngineError> { unimplemented!() } @@ -135,19 +150,19 @@ impl ContainerRegistry for DOCR { unimplemented!() } - fn on_create(&self) -> Result<(), ContainerRegistryError> { + fn on_create(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_create_error(&self) -> Result<(), ContainerRegistryError> { + fn on_create_error(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_delete(&self) -> Result<(), ContainerRegistryError> { + fn on_delete(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_delete_error(&self) -> Result<(), ContainerRegistryError> { + fn on_delete_error(&self) -> Result<(), EngineError> { unimplemented!() } @@ -156,10 +171,12 @@ impl ContainerRegistry for DOCR { } // https://www.digitalocean.com/docs/images/container-registry/how-to/use-registry-docker-kubernetes/ - fn push(&self, image: &Image, _force_push: bool) -> Result { + fn push(&self, image: &Image, _force_push: bool) -> Result { let image = image.clone(); - //TODO instead use get_or_create_repository - self.create_repository(&image); + // TODO 1/ instead use get_or_create_repository + // TODO 2/ does an error is returned if the repository already exist or not? + self.create_repository(&image)?; + match cmd::utilities::exec( "doctl", vec![ @@ -170,19 +187,23 @@ impl ContainerRegistry for DOCR { self.api_key.as_str(), ], ) { - Err(err) => match err { - CmdError::Exec(_exit_status) => return Err(PushError::CredentialsError), - CmdError::Io(err) => return Err(PushError::IoError(err)), - CmdError::Unexpected(err) => return Err(PushError::Unknown(err)), - }, + Err(_) => { + return Err( + self.engine_error( + EngineErrorCause::User("Your DOCR account seems to be no longer valid (bad Credentials). \ + Please contact your Organization administrator to fix or change the Credentials."), + format!("failed to login to DOCR {}", self.name_with_id())) + ); + } _ => {} }; + //TODO check force or not let dest = format!("{}:{}", self.registry_name.as_str(), image.tag.as_str()); self.push_image(dest, &image) } - fn push_error(&self, _image: &Image) -> Result { + fn push_error(&self, _image: &Image) -> Result { unimplemented!() } } diff --git a/src/container_registry/ecr.rs b/src/container_registry/ecr.rs index 1d780197..e87f8dd7 100644 --- a/src/container_registry/ecr.rs +++ b/src/container_registry/ecr.rs @@ -12,10 +12,8 @@ use rusoto_sts::{GetCallerIdentityRequest, Sts, StsClient}; use crate::build_platform::Image; use crate::cmd; -use crate::cmd::utilities::CmdError; -use crate::container_registry::{ - ContainerRegistry, ContainerRegistryError, Kind, PushError, PushResult, -}; +use crate::container_registry::{ContainerRegistry, Kind, PushResult}; +use crate::error::{EngineError, EngineErrorCause}; use crate::models::{ Context, Listener, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressListener, ProgressScope, @@ -112,7 +110,7 @@ impl ECR { } } - fn push_image(&self, dest: String, image: &Image) -> Result { + fn push_image(&self, dest: String, image: &Image) -> Result { // READ https://docs.aws.amazon.com/AmazonECR/latest/userguide/docker-push-ecr-image.html // docker tag e9ae3c220b23 aws_account_id.dkr.ecr.region.amazonaws.com/my-web-app @@ -121,11 +119,16 @@ impl ECR { vec!["tag", image.name_with_tag().as_str(), dest.as_str()], self.docker_envs(), ) { - Err(err) => match err { - CmdError::Exec(_exit_status) => return Err(PushError::ImageTagFailed), - CmdError::Io(err) => return Err(PushError::IoError(err)), - CmdError::Unexpected(err) => return Err(PushError::Unknown(err)), - }, + Err(_) => { + return Err(self.engine_error( + EngineErrorCause::Internal, + format!( + "failed to tag image ({}) {:?}", + image.name_with_tag(), + image, + ), + )); + } _ => {} }; @@ -135,11 +138,16 @@ impl ECR { vec!["push", dest.as_str()], self.docker_envs(), ) { - Err(err) => match err { - CmdError::Exec(_exit_status) => return Err(PushError::ImagePushFailed), - CmdError::Io(err) => return Err(PushError::IoError(err)), - CmdError::Unexpected(err) => return Err(PushError::Unknown(err)), - }, + Err(_) => { + return Err(self.engine_error( + EngineErrorCause::Internal, + format!( + "failed to push image {:?} into ECR {}", + image, + self.name_with_id(), + ), + )); + } _ => {} }; @@ -149,7 +157,7 @@ impl ECR { Ok(PushResult { image }) } - fn create_repository(&self, image: &Image) -> Result { + fn create_repository(&self, image: &Image) -> Result { info!("ECR create repository {}", image.name.as_str()); let mut crr = CreateRepositoryRequest::default(); crr.repository_name = image.name.clone(); @@ -158,7 +166,16 @@ impl ECR { match r { Err(err) => match err { RusotoError::Service(ref err) => info!("{:?}", err), - _ => return Err(ContainerRegistryError::from(err)), + _ => { + return Err(self.engine_error( + EngineErrorCause::Internal, + format!( + "can't create ECR repository {} for {}", + image.name.as_str(), + self.name_with_id() + ), + )); + } }, _ => {} } @@ -191,15 +208,19 @@ impl ECR { let r = async_run(self.ecr_client().put_lifecycle_policy(plp)); match r { - Err(err) => Err(ContainerRegistryError::from(err)), + Err(err) => Err(self.engine_error( + EngineErrorCause::Internal, + format!( + "can't set lifecycle policy to ECR repository {} for {}", + image.name.as_str(), + self.name_with_id() + ), + )), _ => Ok(self.get_repository(&image).unwrap()), } } - fn get_or_create_repository( - &self, - image: &Image, - ) -> Result { + fn get_or_create_repository(&self, image: &Image) -> Result { // check if the repository already exists let repository = self.get_repository(&image); if repository.is_some() { @@ -228,13 +249,19 @@ impl ContainerRegistry for ECR { self.name.as_str() } - fn is_valid(&self) -> Result<(), ContainerRegistryError> { + fn is_valid(&self) -> Result<(), EngineError> { let client = StsClient::new_with_client(self.client(), Region::default()); let s = async_run(client.get_caller_identity(GetCallerIdentityRequest::default())); match s { - Ok(_x) => Ok(()), - Err(err) => Err(ContainerRegistryError::from(err)), + Ok(_) => Ok(()), + Err(_) => Err(self.engine_error( + EngineErrorCause::User( + "Your ECR account seems to be no longer valid (bad Credentials). \ + Please contact your Organization administrator to fix or change the Credentials.", + ), + format!("bad ECR credentials for {}", self.name_with_id()), + )), } } @@ -242,28 +269,28 @@ impl ContainerRegistry for ECR { self.listeners.push(listener); } - fn on_create(&self) -> Result<(), ContainerRegistryError> { + fn on_create(&self) -> Result<(), EngineError> { info!("ECR.on_create() called"); Ok(()) } - fn on_create_error(&self) -> Result<(), ContainerRegistryError> { + fn on_create_error(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_delete(&self) -> Result<(), ContainerRegistryError> { + fn on_delete(&self) -> Result<(), EngineError> { unimplemented!() } - fn on_delete_error(&self) -> Result<(), ContainerRegistryError> { + fn on_delete_error(&self) -> Result<(), EngineError> { unimplemented!() } fn does_image_exists(&self, image: &Image) -> bool { - self.get_repository(&image).is_some() + self.get_repository(image).is_some() } - fn push(&self, image: &Image, force_push: bool) -> Result { + fn push(&self, image: &Image, force_push: bool) -> Result { let r = async_run( self.ecr_client() .get_authorization_token(GetAuthorizationTokenRequest::default()), @@ -286,18 +313,43 @@ impl ContainerRegistry for ECR { ad.clone().proxy_endpoint.unwrap(), ) } - None => return Err(PushError::RepositoryInitFailure), + None => { + return Err(self.engine_error( + EngineErrorCause::Internal, + format!( + "failed to retrieve credentials and endpoint URL from ECR {}", + self.name_with_id(), + ), + )); + } }, - _ => return Err(PushError::RepositoryInitFailure), + _ => { + return Err(self.engine_error( + EngineErrorCause::Internal, + format!( + "failed to retrieve credentials and endpoint URL from ECR {}", + self.name_with_id(), + ), + )); + } }; let repository = match if force_push { - self.create_repository(&image) + self.create_repository(image) } else { - self.get_or_create_repository(&image) + self.get_or_create_repository(image) } { Ok(r) => r, - _ => return Err(PushError::RepositoryInitFailure), + _ => { + return Err(self.engine_error( + EngineErrorCause::Internal, + format!( + "failed to create ECR repository for {} with image {:?}", + self.name_with_id(), + image, + ), + )); + } }; match cmd::utilities::exec_with_envs( @@ -312,11 +364,12 @@ impl ContainerRegistry for ECR { ], self.docker_envs(), ) { - Err(err) => match err { - CmdError::Exec(_exit_status) => return Err(PushError::CredentialsError), - CmdError::Io(err) => return Err(PushError::IoError(err)), - CmdError::Unexpected(err) => return Err(PushError::Unknown(err)), - }, + Err(_) => return Err( + self.engine_error( + EngineErrorCause::User("Your ECR account seems to be no longer valid (bad Credentials). \ + Please contact your Organization administrator to fix or change the Credentials."), + format!("failed to login to ECR {}", self.name_with_id())) + ), _ => {} }; @@ -373,7 +426,7 @@ impl ContainerRegistry for ECR { self.push_image(dest, image) } - fn push_error(&self, image: &Image) -> Result { + fn push_error(&self, image: &Image) -> Result { // TODO change this Ok(PushResult { image: image.clone(), diff --git a/src/container_registry/mod.rs b/src/container_registry/mod.rs index 531cb77b..c6fdcc1a 100644 --- a/src/container_registry/mod.rs +++ b/src/container_registry/mod.rs @@ -5,6 +5,7 @@ use rusoto_core::RusotoError; use serde::{Deserialize, Serialize}; use crate::build_platform::Image; +use crate::error::{EngineError, EngineErrorCause, EngineErrorScope}; use crate::models::{Context, Listener, ProgressListener}; pub mod docker_hub; @@ -16,61 +17,38 @@ pub trait ContainerRegistry { fn kind(&self) -> Kind; fn id(&self) -> &str; fn name(&self) -> &str; - fn is_valid(&self) -> Result<(), ContainerRegistryError>; + fn name_with_id(&self) -> String { + format!("{} ({})", self.name(), self.id()) + } + fn is_valid(&self) -> Result<(), EngineError>; fn add_listener(&mut self, listener: Listener); - fn on_create(&self) -> Result<(), ContainerRegistryError>; - fn on_create_error(&self) -> Result<(), ContainerRegistryError>; - fn on_delete(&self) -> Result<(), ContainerRegistryError>; - fn on_delete_error(&self) -> Result<(), ContainerRegistryError>; + fn on_create(&self) -> Result<(), EngineError>; + fn on_create_error(&self) -> Result<(), EngineError>; + fn on_delete(&self) -> Result<(), EngineError>; + fn on_delete_error(&self) -> Result<(), EngineError>; fn does_image_exists(&self, image: &Image) -> bool; - fn push(&self, image: &Image, force_push: bool) -> Result; - fn push_error(&self, image: &Image) -> Result; + fn push(&self, image: &Image, force_push: bool) -> Result; + fn push_error(&self, image: &Image) -> Result; + fn engine_error_scope(&self) -> EngineErrorScope { + EngineErrorScope::ContainerRegistry(self.id().to_string(), self.name().to_string()) + } + fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError { + EngineError::new( + cause, + self.engine_error_scope(), + self.context().execution_id(), + Some(message), + ) + } } pub struct PushResult { pub image: Image, } -#[derive(Debug)] -pub enum PushError { - RepositoryInitFailure, - CredentialsError, - IoError(std::io::Error), - ImageTagFailed, - ImagePushFailed, - ImageAlreadyExists, - Unknown(String), -} - #[derive(Serialize, Deserialize, Clone)] pub enum Kind { DockerHub, ECR, DOCR, } - -#[derive(Debug, Eq, PartialEq)] -pub enum ContainerRegistryError { - Credentials, - Unknown, -} - -impl From> for ContainerRegistryError { - fn from(error: RusotoError) -> Self { - match error { - RusotoError::Credentials(_) => ContainerRegistryError::Credentials, - RusotoError::Service(_) => ContainerRegistryError::Unknown, - RusotoError::HttpDispatch(_) => ContainerRegistryError::Unknown, - RusotoError::Validation(_) => ContainerRegistryError::Unknown, - RusotoError::ParseError(_) => ContainerRegistryError::Unknown, - RusotoError::Unknown(e) => { - if e.status == 403 { - ContainerRegistryError::Credentials - } else { - ContainerRegistryError::Unknown - } - } - RusotoError::Blocking => ContainerRegistryError::Unknown, - } - } -} diff --git a/src/dns_provider/cloudflare.rs b/src/dns_provider/cloudflare.rs index ee021249..35a330e9 100644 --- a/src/dns_provider/cloudflare.rs +++ b/src/dns_provider/cloudflare.rs @@ -1,6 +1,7 @@ use std::net::Ipv4Addr; -use crate::dns_provider::{DnsProvider, DnsProviderError, Kind}; +use crate::dns_provider::{DnsProvider, Kind}; +use crate::error::{EngineError, EngineErrorCause}; use crate::models::Context; pub struct Cloudflare { @@ -65,9 +66,15 @@ impl DnsProvider for Cloudflare { vec![Ipv4Addr::new(1, 1, 1, 1), Ipv4Addr::new(1, 0, 0, 1)] } - fn is_valid(&self) -> Result<(), DnsProviderError> { + fn is_valid(&self) -> Result<(), EngineError> { if self.cloudflare_api_token.is_empty() || self.cloudflare_email.is_empty() { - Err(DnsProviderError::Credentials) + Err(self.engine_error( + EngineErrorCause::User( + "Your Cloudflare account seems to be no longer valid (bad Credentials). \ + Please contact your Organization administrator to fix or change the Credentials.", + ), + format!("bad Cloudflare credentials for {}", self.name_with_id()), + )) } else { Ok(()) } diff --git a/src/dns_provider/mod.rs b/src/dns_provider/mod.rs index faa653d3..a0ee24fa 100644 --- a/src/dns_provider/mod.rs +++ b/src/dns_provider/mod.rs @@ -2,6 +2,7 @@ use std::net::Ipv4Addr; use serde::{Deserialize, Serialize}; +use crate::error::{EngineError, EngineErrorCause, EngineErrorScope}; use crate::models::Context; pub mod cloudflare; @@ -16,15 +17,23 @@ pub trait DnsProvider { fn kind(&self) -> Kind; fn id(&self) -> &str; fn name(&self) -> &str; + fn name_with_id(&self) -> String { + format!("{} ({})", self.name(), self.id()) + } fn account(&self) -> &str; fn token(&self) -> &str; fn domain(&self) -> &str; fn resolvers(&self) -> Vec; - fn is_valid(&self) -> Result<(), DnsProviderError>; -} - -#[derive(Debug, Eq, PartialEq)] -pub enum DnsProviderError { - Credentials, - Unknown, + fn is_valid(&self) -> Result<(), EngineError>; + fn engine_error_scope(&self) -> EngineErrorScope { + EngineErrorScope::DnsProvider(self.id().to_string(), self.name().to_string()) + } + fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError { + EngineError::new( + cause, + self.engine_error_scope(), + self.context().execution_id(), + Some(message), + ) + } } diff --git a/src/engine.rs b/src/engine.rs index 37ea671c..40087a0a 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,8 +3,8 @@ use std::borrow::Borrow; use crate::build_platform::BuildPlatform; use crate::cloud_provider::CloudProvider; use crate::container_registry::ContainerRegistry; -use crate::dns_provider::{DnsProvider, DnsProviderError}; -use crate::error::ConfigurationError; +use crate::dns_provider::DnsProvider; +use crate::error::EngineError; use crate::models::Context; use crate::session::Session; @@ -50,44 +50,22 @@ impl<'a> Engine { pub fn cloud_provider(&self) -> &dyn CloudProvider { self.cloud_provider.borrow() } + pub fn dns_provider(&self) -> &dyn DnsProvider { self.dns_provider.borrow() } - pub fn is_valid(&self) -> Result<(), ConfigurationError> { - match self.build_platform.is_valid() { - Ok(_) => {} - Err(err) => { - return Err(ConfigurationError::BuildPlatform(err)); - } - } - - match self.container_registry.is_valid() { - Ok(_) => {} - Err(err) => { - return Err(ConfigurationError::ContainerRegistry(err)); - } - } - - match self.cloud_provider.is_valid() { - Ok(_) => {} - Err(err) => { - return Err(ConfigurationError::CloudProvider(err)); - } - } - - match self.dns_provider.is_valid() { - Ok(_) => {} - Err(err) => { - return Err(ConfigurationError::DnsProvider(err)); - } - } + pub fn is_valid(&self) -> Result<(), EngineError> { + self.build_platform.is_valid()?; + self.container_registry.is_valid()?; + self.cloud_provider.is_valid()?; + self.dns_provider.is_valid()?; Ok(()) } /// check and init the connection to all the services - pub fn session(&'a self) -> Result, ConfigurationError> { + pub fn session(&'a self) -> Result, EngineError> { match self.is_valid() { Ok(_) => Ok(Session::<'a> { engine: self }), Err(err) => Err(err), diff --git a/src/error.rs b/src/error.rs index 702fbb35..4e6d35eb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,12 +1,117 @@ -use crate::build_platform::error::BuildPlatformError; -use crate::cloud_provider::CloudProviderError; -use crate::container_registry::ContainerRegistryError; -use crate::dns_provider::DnsProviderError; +use std::error::Error; +use std::process::ExitStatus; + +use rusoto_core::RusotoError; + +pub type Type = String; +pub type Id = String; +pub type Name = String; #[derive(Debug)] -pub enum ConfigurationError { - BuildPlatform(BuildPlatformError), - ContainerRegistry(ContainerRegistryError), - CloudProvider(CloudProviderError), - DnsProvider(DnsProviderError), +pub struct EngineError { + pub cause: EngineErrorCause, + pub scope: EngineErrorScope, + pub execution_id: String, + pub message: Option, +} + +impl EngineError { + pub fn new( + cause: EngineErrorCause, + scope: EngineErrorScope, + execution_id: T, + message: Option, + ) -> Self + where + T: Into, + S: Into, + { + EngineError { + cause, + scope, + execution_id: execution_id.into(), + message: match message { + Some(message) => Some(message.into()), + _ => None, + }, + } + } +} + +#[derive(Debug)] +pub enum EngineErrorScope { + Engine, + BuildPlatform(Id, Name), + ContainerRegistry(Id, Name), + CloudProvider(Id, Name), + Kubernetes(Id, Name), + DnsProvider(Id, Name), + Environment(Id, Name), + Database(Id, Type, Name), + Application(Id, Name), + Router(Id, Name), + ExternalService(Id, Name), +} + +#[derive(Debug)] +pub enum EngineErrorCause { + Internal, + User(&'static str), +} + +#[derive(Debug)] +pub struct SimpleError { + pub kind: SimpleErrorKind, + pub message: Option, +} + +#[derive(Debug)] +pub enum SimpleErrorKind { + Command(ExitStatus), + Other, +} + +impl SimpleError { + pub fn new>(kind: SimpleErrorKind, message: Option) -> Self { + SimpleError { + kind, + message: match message { + Some(message) => Some(message.into()), + _ => None, + }, + } + } +} + +impl From for SimpleError { + fn from(err: std::io::Error) -> Self { + SimpleError::new(SimpleErrorKind::Other, Some(err.to_string())) + } +} + +pub fn from_simple_error_to_engine_error>( + scope: EngineErrorScope, + execution_id: T, + input: Result, +) -> Result { + match input { + Err(simple_error) => { + let message = match simple_error.kind { + SimpleErrorKind::Command(exit_status) => format!( + "{} (exit status: {})", + simple_error.message.unwrap_or("".into()), + exit_status + ), + SimpleErrorKind::Other => simple_error.message.unwrap_or("".into()), + }; + + Err(EngineError::new( + EngineErrorCause::Internal, + scope, + execution_id, + Some(message), + )) + } + Ok(x) => Ok(x), + } } diff --git a/src/s3.rs b/src/s3.rs index fb9161bd..d299f2c8 100644 --- a/src/s3.rs +++ b/src/s3.rs @@ -1,3 +1,11 @@ +use std::fmt::Display; +use std::fs::{read_to_string, File}; +use std::io::{Error, ErrorKind, Read, Write}; +use std::os::unix::fs::PermissionsExt; +use std::path::Path; +use std::str::FromStr; +use std::{fs, io}; + use retry::delay::Fibonacci; use retry::OperationResult; use rusoto_core::{Client, HttpClient, Region, RusotoError}; @@ -7,23 +15,20 @@ use rusoto_s3::{ GetObjectRequest, ListObjectsV2Output, ListObjectsV2Request, PutBucketVersioningRequest, S3Client, VersioningConfiguration, S3, }; -use std::fmt::Display; -use std::fs::{read_to_string, File}; -use std::io::{Error, ErrorKind, Read, Write}; -use std::os::unix::fs::PermissionsExt; -use std::path::Path; -use std::{fs, io}; + +use crate::cmd::utilities::exec_with_envs; +use crate::error::{SimpleError, SimpleErrorKind}; +use crate::runtime::async_run; + pub const AWS_REGION_FOR_S3_US: &str = "ap-south-1"; -use crate::cmd::utilities::{exec_with_envs, CmdError}; -use crate::runtime::async_run; -use std::str::FromStr; +pub type FileContent = String; pub fn create_bucket( access_key_id: &str, secret_access_key: &str, bucket_name: &str, -) -> Result<(), CmdError> { +) -> Result<(), SimpleError> { exec_with_envs( "aws", vec!["s3api", "create-bucket", "--bucket", &bucket_name], @@ -34,15 +39,13 @@ pub fn create_bucket( ) } -pub type FileContent = String; - pub fn get_object( access_key_id: &str, secret_access_key: &str, region: &Region, bucket_name: &str, object_key: &str, -) -> Result { +) -> Result { let credentials = StaticProvider::new( access_key_id.to_string(), secret_access_key.to_string(), @@ -59,12 +62,12 @@ pub fn get_object( let get_object_output = s3_client.get_object(or); let r = async_run(get_object_output); - let _err = Error::new( - ErrorKind::Other, - format!( + let _err = SimpleError::new( + SimpleErrorKind::Other, + Some(format!( "something goes wrong while getting object {} in the S3 bucket {}", object_key, bucket_name - ), + )), ); match r { @@ -90,9 +93,9 @@ pub fn get_object( RusotoError::Service(s) => match s { GetObjectError::NoSuchKey(x) => { info!("no such key '{}': {}", object_key, x.as_str()); - Err(Error::new( - ErrorKind::NotFound, - format!("no such key '{}': {}", object_key, x.as_str()), + Err(SimpleError::new( + SimpleErrorKind::Other, + Some(format!("no such key '{}': {}", object_key, x.as_str())), )) } }, @@ -107,7 +110,10 @@ pub fn get_object( match r_from_aws_cli { Ok(..) => Ok(r_from_aws_cli.unwrap()), Err(err) => { - error!("{}", err); + if let Some(message) = err.message { + error!("{}", message); + } + Err(_err) } } @@ -125,21 +131,19 @@ fn get_object_via_aws_cli( secret_access_key: &str, bucket_name: &str, object_key: &str, -) -> Result { +) -> Result { let s3_url = format!("s3://{}/{}", bucket_name, object_key); let local_path = format!("/tmp/{}", object_key); - let r = exec_with_envs( + + exec_with_envs( "aws", vec!["s3", "cp", &s3_url, &local_path], vec![ ("AWS_ACCESS_KEY_ID", &access_key_id), ("AWS_SECRET_ACCESS_KEY", &secret_access_key), ], - ); - match r { - Err(e) => return Err(Error::new(ErrorKind::Other, e)), - _ => {} - }; + )?; + let s = read_to_string(&local_path)?; Ok(s) } @@ -151,7 +155,7 @@ pub fn get_kubernetes_config_file

( kubernetes_config_bucket_name: &str, kubernetes_config_object_key: &str, file_path: P, -) -> Result +) -> Result where P: AsRef, { @@ -183,9 +187,9 @@ where let file_content = match file_content_result { Ok(file_content) => file_content, Err(_) => { - return Err(Error::new( - ErrorKind::InvalidData, - "file content is empty (retry failed multiple times) - which is not the expected content - what's wrong?", + return Err(SimpleError::new( + SimpleErrorKind::Other, + Some("file content is empty (retry failed multiple times) - which is not the expected content - what's wrong?"), )); } }; @@ -204,7 +208,7 @@ pub fn list_objects_in( access_key_id: &str, secret_access_key: &str, bucket_name: &str, -) -> Result { +) -> Result { let credentials = StaticProvider::new( access_key_id.to_string(), secret_access_key.to_string(), @@ -213,13 +217,19 @@ pub fn list_objects_in( ); let client = Client::new_with(credentials, HttpClient::new().unwrap()); let s3_client = S3Client::new_with_client(client, get_default_region_for_us()); + let mut list_request = ListObjectsV2Request::default(); list_request.bucket = bucket_name.to_string(); + let lis_object = s3_client.list_objects_v2(list_request); let objects_in = async_run(lis_object); + match objects_in { Ok(objects) => Ok(objects), - Err(err) => Err(Error::new(ErrorKind::Other, err)), + Err(err) => Err(SimpleError::new( + SimpleErrorKind::Other, + Some(format!("error listing objects from s3 {:?}", err)), + )), } } @@ -228,7 +238,7 @@ pub fn delete_bucket( access_key_id: &str, secret_access_key: &str, bucket_name: &str, -) -> Result<(), CmdError> { +) -> Result<(), SimpleError> { info!("Deleting S3 Bucket {}", bucket_name.clone()); match exec_with_envs( "aws", @@ -249,7 +259,10 @@ pub fn delete_bucket( return Ok(o); } Err(e) => { - error!("while deleting bucket {}", e); + error!( + "while deleting bucket {}", + e.message.as_ref().unwrap_or(&"".into()) + ); return Err(e); } } @@ -265,7 +278,7 @@ pub fn push_object( bucket_name: &str, object_key: &str, local_file_path: &str, -) -> Result<(), CmdError> { +) -> Result<(), SimpleError> { info!( "Pushing object {} to bucket {}", local_file_path.clone(), @@ -289,7 +302,10 @@ pub fn push_object( return Ok(o); } Err(e) => { - error!("While uploading object {}", e); + error!( + "While uploading object {}", + e.message.as_ref().unwrap_or(&"".into()) + ); return Err(e); } } diff --git a/src/template.rs b/src/template.rs index a9094212..cad1d3ff 100644 --- a/src/template.rs +++ b/src/template.rs @@ -5,6 +5,7 @@ use std::io::{Error, ErrorKind, Write}; use std::os::unix::fs::PermissionsExt; use std::path::Path; +use crate::error::{SimpleError, SimpleErrorKind}; use tera::Error as TeraError; use tera::{Context, Tera}; use walkdir::WalkDir; @@ -13,7 +14,7 @@ pub fn generate_and_copy_all_files_into_dir( from_dir: S, to_dir: P, context: &Context, -) -> Result<(), Error> +) -> Result<(), SimpleError> where S: AsRef + Copy, P: AsRef + Copy, @@ -49,7 +50,7 @@ where }; error!("{}", error_msg.as_str()); - return Err(Error::new(ErrorKind::InvalidData, error_msg)); + return Err(SimpleError::new(SimpleErrorKind::Other, Some(error_msg))); } }; @@ -61,12 +62,15 @@ where Ok(()) } -pub fn copy_non_template_files(from: S, to: P) -> Result<(), Error> +pub fn copy_non_template_files(from: S, to: P) -> Result<(), SimpleError> where S: AsRef, P: AsRef, { - crate::fs::copy_files(from.as_ref(), to.as_ref(), true) + match crate::fs::copy_files(from.as_ref(), to.as_ref(), true) { + Err(err) => Err(SimpleError::from(err)), + Ok(x) => Ok(x), + } } pub fn generate_j2_template_files

( @@ -117,7 +121,7 @@ where pub fn write_rendered_templates( rendered_templates: &[RenderedTemplate], into: &Path, -) -> Result<(), Error> { +) -> Result<(), SimpleError> { for rt in rendered_templates { let dest = format!("{}/{}", into.to_str().unwrap(), rt.path_and_file_name()); diff --git a/src/transaction.rs b/src/transaction.rs index 74c5bff3..75e5fe04 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -3,13 +3,11 @@ use std::thread; use serde::{Deserialize, Serialize}; -use crate::build_platform::BuildError; -use crate::cloud_provider::kubernetes::{Kubernetes, KubernetesError}; -use crate::cloud_provider::service::ServiceError; +use crate::cloud_provider::kubernetes::Kubernetes; use crate::cloud_provider::service::{Application, Service}; -use crate::cloud_provider::DeployError; -use crate::container_registry::{PushError, PushResult}; +use crate::container_registry::PushResult; use crate::engine::Engine; +use crate::error::EngineError; use crate::models::{ Action, Environment, EnvironmentAction, EnvironmentError, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope, @@ -30,10 +28,7 @@ impl<'a> Transaction<'a> { } } - pub fn create_kubernetes( - &mut self, - kubernetes: &'a dyn Kubernetes, - ) -> Result<(), KubernetesError> { + pub fn create_kubernetes(&mut self, kubernetes: &'a dyn Kubernetes) -> Result<(), EngineError> { match kubernetes.is_valid() { Ok(_) => { self.steps.push(Step::CreateKubernetes(kubernetes)); @@ -43,10 +38,7 @@ impl<'a> Transaction<'a> { } } - pub fn delete_kubernetes( - &mut self, - kubernetes: &'a dyn Kubernetes, - ) -> Result<(), KubernetesError> { + pub fn delete_kubernetes(&mut self, kubernetes: &'a dyn Kubernetes) -> Result<(), EngineError> { match kubernetes.is_valid() { Ok(_) => { self.steps.push(Step::DeleteKubernetes(kubernetes)); @@ -143,7 +135,7 @@ impl<'a> Transaction<'a> { &self, environment: &Environment, option: &DeploymentOption, - ) -> Result>, BuildError> { + ) -> Result>, EngineError> { let external_services_to_build = environment .external_services .iter() @@ -238,7 +230,7 @@ impl<'a> Transaction<'a> { &self, applications: Vec>, option: &DeploymentOption, - ) -> Result, PushResult)>, PushError> { + ) -> Result, PushResult)>, EngineError> { let application_and_push_results: Vec<_> = applications .into_iter() .map(|mut app| { @@ -276,18 +268,13 @@ impl<'a> Transaction<'a> { environment: &crate::cloud_provider::environment::Environment, ) -> TransactionResult { match environment.is_valid() { - Err(service_error) => { - warn!("ROLLBACK STARTED! an error occurred {:?}", service_error); + Err(engine_error) => { + warn!("ROLLBACK STARTED! an error occurred {:?}", engine_error); return match self.rollback() { - Ok(_) => { - TransactionResult::Rollback(CommitError::NotValidService(service_error)) - } + Ok(_) => TransactionResult::Rollback(engine_error), Err(err) => { error!("ROLLBACK FAILED! fatal error: {:?}", err); - TransactionResult::UnrecoverableError( - CommitError::NotValidService(service_error), - err, - ) + TransactionResult::UnrecoverableError(engine_error, err) } }; } @@ -303,14 +290,14 @@ impl<'a> Transaction<'a> { Step::CreateKubernetes(kubernetes) => { // revert kubernetes creation match kubernetes.on_create_error() { - Err(err) => return Err(RollbackError::CreateKubernetes(err)), + Err(err) => return Err(RollbackError::CommitError(err)), _ => {} }; } Step::DeleteKubernetes(kubernetes) => { // revert kubernetes deletion match kubernetes.on_delete_error() { - Err(err) => return Err(RollbackError::DeleteKubernetes(err)), + Err(err) => return Err(RollbackError::CommitError(err)), _ => {} }; } @@ -408,14 +395,7 @@ impl<'a> Transaction<'a> { let _ = match action { Ok(_) => {} - Err(err) => { - return Err(match failover_environment.action { - Action::Create => RollbackError::DeployEnvironment(err), - Action::Pause => RollbackError::PauseEnvironment(err), - Action::Delete => RollbackError::DeleteEnvironment(err), - Action::Nothing => RollbackError::Error, // it can't happens - }); - } + Err(err) => return Err(RollbackError::CommitError(err)), }; Ok(()) @@ -433,14 +413,7 @@ impl<'a> Transaction<'a> { let _ = match action { Ok(_) => {} - Err(err) => { - return Err(match te.action { - Action::Create => RollbackError::DeployEnvironment(err), - Action::Pause => RollbackError::PauseEnvironment(err), - Action::Delete => RollbackError::DeleteEnvironment(err), - Action::Nothing => RollbackError::Error, // it can't happens - }); - } + Err(err) => return Err(RollbackError::CommitError(err)), }; Err(RollbackError::NoFailoverEnvironment) @@ -463,15 +436,10 @@ impl<'a> Transaction<'a> { Err(err) => { warn!("ROLLBACK STARTED! an error occurred {:?}", err); match self.rollback() { - Ok(_) => { - TransactionResult::Rollback(CommitError::CreateKubernetes(err)) - } + Ok(_) => TransactionResult::Rollback(err), Err(e) => { error!("ROLLBACK FAILED! fatal error: {:?}", e); - TransactionResult::UnrecoverableError( - CommitError::CreateKubernetes(err), - e, - ) + TransactionResult::UnrecoverableError(err, e) } } } @@ -484,15 +452,10 @@ impl<'a> Transaction<'a> { Err(err) => { warn!("ROLLBACK STARTED! an error occurred {:?}", err); match self.rollback() { - Ok(_) => { - TransactionResult::Rollback(CommitError::DeleteKubernetes(err)) - } + Ok(_) => TransactionResult::Rollback(err), Err(e) => { error!("ROLLBACK FAILED! fatal error: {:?}", e); - TransactionResult::UnrecoverableError( - CommitError::DeleteKubernetes(err), - e, - ) + TransactionResult::UnrecoverableError(err, e) } } } @@ -514,9 +477,9 @@ impl<'a> Transaction<'a> { Ok(applications) } - Err(err) => Err(CommitError::PushImage(err)), + Err(err) => Err(err), }, - Err(err) => Err(CommitError::BuildImage(err)), + Err(err) => Err(err), }; if apps_result.is_err() { @@ -542,7 +505,6 @@ impl<'a> Transaction<'a> { *environment_action, &applications_by_environment, |qe_env| kubernetes.deploy_environment(qe_env), - |err| CommitError::DeployEnvironment(err), ) { TransactionResult::Ok => {} err => return err, @@ -555,7 +517,6 @@ impl<'a> Transaction<'a> { *environment_action, &applications_by_environment, |qe_env| kubernetes.pause_environment(qe_env), - |err| CommitError::PauseEnvironment(err), ) { TransactionResult::Ok => {} err => return err, @@ -568,7 +529,6 @@ impl<'a> Transaction<'a> { *environment_action, &applications_by_environment, |qe_env| kubernetes.delete_environment(qe_env), - |err| CommitError::DeleteEnvironment(err), ) { TransactionResult::Ok => {} err => return err, @@ -580,17 +540,15 @@ impl<'a> Transaction<'a> { TransactionResult::Ok } - fn commit_environment( + fn commit_environment( &self, kubernetes: &dyn Kubernetes, environment_action: &EnvironmentAction, applications_by_environment: &HashMap<&Environment, Vec>>, action_fn: F, - commit_error: E, ) -> TransactionResult where - F: Fn(&crate::cloud_provider::environment::Environment) -> Result<(), KubernetesError>, - E: Fn(KubernetesError) -> CommitError, + F: Fn(&crate::cloud_provider::environment::Environment) -> Result<(), EngineError>, { let target_environment = match environment_action { EnvironmentAction::Environment(te) => te, @@ -668,10 +626,10 @@ impl<'a> Transaction<'a> { let _ = match action_fn(&qe_environment) { Err(err) => { let rollback_result = match self.rollback() { - Ok(_) => TransactionResult::Rollback(commit_error(err)), + Ok(_) => TransactionResult::Rollback(err), Err(rollback_err) => { error!("ROLLBACK FAILED! fatal error: {:?}", rollback_err); - TransactionResult::UnrecoverableError(commit_error(err), rollback_err) + TransactionResult::UnrecoverableError(err, rollback_err) } }; @@ -756,36 +714,15 @@ impl<'a> Clone for Step<'a> { } } -#[derive(Debug)] -pub enum CommitError { - CreateKubernetes(KubernetesError), - DeleteKubernetes(KubernetesError), - DeployEnvironment(KubernetesError), - PauseEnvironment(KubernetesError), - DeleteEnvironment(KubernetesError), - NotValidService(ServiceError), - BuildImage(BuildError), - PushImage(PushError), - DeployImage(DeployError), -} - #[derive(Debug)] pub enum RollbackError { - CreateKubernetes(KubernetesError), - DeleteKubernetes(KubernetesError), - DeployEnvironment(KubernetesError), - PauseEnvironment(KubernetesError), - DeleteEnvironment(KubernetesError), - NotValidService(ServiceError), - BuildImage(BuildError), - PushImage(PushError), - DeployImage(DeployError), + CommitError(EngineError), NoFailoverEnvironment, - Error, + Nothing, } pub enum TransactionResult { Ok, - Rollback(CommitError), - UnrecoverableError(CommitError, RollbackError), + Rollback(EngineError), + UnrecoverableError(EngineError, RollbackError), } diff --git a/tests/aws/aws_kubernetes.rs b/tests/aws/aws_kubernetes.rs index 8f9c2c14..c0c2b67d 100644 --- a/tests/aws/aws_kubernetes.rs +++ b/tests/aws/aws_kubernetes.rs @@ -6,9 +6,8 @@ use qovery_engine::build_platform::GitRepository; use qovery_engine::cloud_provider::aws::kubernetes::node::Node; use qovery_engine::cloud_provider::aws::kubernetes::EKS; use qovery_engine::cloud_provider::aws::AWS; -use qovery_engine::cloud_provider::kubernetes::{Kubernetes, KubernetesError}; +use qovery_engine::cloud_provider::kubernetes::Kubernetes; use qovery_engine::cloud_provider::CloudProvider; -use qovery_engine::cmd::utilities::CmdError; use qovery_engine::dns_provider::cloudflare::Cloudflare; use qovery_engine::git::Credentials; use qovery_engine::models::{Clone2, GitCredentials}; diff --git a/tests/unit/s3.rs b/tests/unit/s3.rs index 1e29460a..2470f890 100644 --- a/tests/unit/s3.rs +++ b/tests/unit/s3.rs @@ -30,8 +30,8 @@ fn delete_s3_bucket() { bucket_name, ); match creation { - Ok(out) => println!("Yippee Ki Yay"), - Err(e) => println!("While creating the bucket {}", e), + Ok(_) => println!("Yippee Ki Yay"), + Err(e) => println!("While creating the bucket {}", e.message.unwrap()), } let delete = delete_bucket( @@ -41,6 +41,6 @@ fn delete_s3_bucket() { ); match delete { Ok(out) => println!("Yippee Ki Yay"), - Err(e) => println!("While deleting the bucket {}", e), + Err(e) => println!("While deleting the bucket {}", e.message.unwrap()), } }