diff --git a/src/build_platform/local_docker.rs b/src/build_platform/local_docker.rs index 49dfb322..849baf6e 100644 --- a/src/build_platform/local_docker.rs +++ b/src/build_platform/local_docker.rs @@ -6,10 +6,12 @@ use git2::{Cred, CredentialType}; use sysinfo::{Disk, DiskExt, SystemExt}; use crate::build_platform::{docker, Build, BuildPlatform, BuildResult, CacheResult, Credentials, Image, Kind}; -use crate::cmd::command::{CommandError, QoveryCommand}; -use crate::error::{EngineError, EngineErrorCause, EngineErrorScope, SimpleError}; +use crate::cmd::command::QoveryCommand; +use crate::errors::{CommandError, EngineError, Tag}; +use crate::events::{EngineEvent, EventDetails, EventMessage, ToTransmitter, Transmitter}; use crate::fs::workspace_directory; use crate::git; +use crate::logger::{LogLevel, Logger}; use crate::models::{ Context, Listen, Listener, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope, }; @@ -30,15 +32,17 @@ pub struct LocalDocker { id: String, name: String, listeners: Listeners, + logger: Box, } impl LocalDocker { - pub fn new(context: Context, id: &str, name: &str) -> Self { + pub fn new(context: Context, id: &str, name: &str, logger: Box) -> Self { LocalDocker { context, id: id.to_string(), name: name.to_string(), listeners: vec![], + logger, } } @@ -64,9 +68,14 @@ impl LocalDocker { match fs::read(dockerfile_path) { Ok(bytes) => Ok(bytes), Err(err) => { - let error_msg = format!("Can't read Dockerfile '{}'", dockerfile_path); - error!("{}, error: {:?}", error_msg, err); - Err(self.engine_error(EngineErrorCause::Internal, error_msg)) + let engine_error = EngineError::new_docker_cannot_read_dockerfile( + self.get_event_details(), + dockerfile_path.to_string(), + CommandError::new(err.to_string(), None), + ); + self.logger + .log(LogLevel::Error, EngineEvent::Error(engine_error.clone(), None)); + Err(engine_error) } } } @@ -102,9 +111,14 @@ impl LocalDocker { let env_var_args = match docker::match_used_env_var_args(env_var_args, dockerfile_content) { Ok(env_var_args) => env_var_args, Err(err) => { - let error_msg = format!("Can't extract env vars from Dockerfile '{}'", dockerfile_complete_path); - error!("{}, error: {:?}", error_msg, err); - return Err(self.engine_error(EngineErrorCause::Internal, error_msg)); + let engine_error = EngineError::new_docker_cannot_extract_env_vars_from_dockerfile( + self.get_event_details(), + dockerfile_complete_path.to_string(), + CommandError::new(err.to_string(), None), + ); + self.logger + .log(LogLevel::Error, EngineEvent::Error(engine_error.clone(), None)); + return Err(engine_error); } }; @@ -130,7 +144,10 @@ impl LocalDocker { let exit_status = cmd.exec_with_abort( Duration::minutes(BUILD_DURATION_TIMEOUT_MIN), |line| { - info!("{}", line); + self.logger.log( + LogLevel::Info, + EngineEvent::Info(self.get_event_details(), EventMessage::new_from_safe(line.to_string())), + ); lh.deployment_in_progress(ProgressInfo::new( ProgressScope::Application { @@ -142,7 +159,10 @@ impl LocalDocker { )); }, |line| { - error!("{}", line); + self.logger.log( + LogLevel::Warning, + EngineEvent::Warning(self.get_event_details(), EventMessage::new_from_safe(line.to_string())), + ); lh.deployment_in_progress(ProgressInfo::new( ProgressScope::Application { @@ -158,18 +178,10 @@ impl LocalDocker { match exit_status { Ok(_) => Ok(BuildResult { build }), - Err(CommandError::Killed(msg)) => Err( - self.engine_error(EngineErrorCause::Canceled, msg) - ), - Err(err) => Err(self.engine_error( - EngineErrorCause::User( - "It looks like there is something wrong in your Dockerfile. Try building the application locally with `docker build --no-cache`.", - ), - format!( - "error while building container image {}. Error: {:?}", - self.name_with_id(), - err - ), + Err(err) => Err(EngineError::new_docker_cannot_build_container_image( + self.get_event_details(), + self.name_with_id(), + CommandError::new(format!("{:?}", err), None), )), } } @@ -187,7 +199,8 @@ impl LocalDocker { let args = self.context.docker_build_options(); - let mut exit_status: Result<(), CommandError> = Ok(()); + let mut exit_status: Result<(), CommandError> = + Err(CommandError::new_from_safe_message("No builder names".to_string())); for builder_name in BUILDPACKS_BUILDERS.iter() { let mut buildpacks_args = if !use_build_cache { @@ -250,12 +263,13 @@ impl LocalDocker { self.context.execution_id(), )); - let err = EngineError::new( - EngineErrorCause::Internal, - EngineErrorScope::Engine, - self.context.execution_id().to_string(), - Some(msg), + let err = EngineError::new_buildpack_invalid_language_format( + self.get_event_details(), + buildpacks_language.to_string(), ); + + self.logger.log(LogLevel::Error, EngineEvent::Error(err.clone(), None)); + return Err(err); } } @@ -273,34 +287,45 @@ impl LocalDocker { // buildpacks build let mut cmd = QoveryCommand::new("pack", &buildpacks_args, &self.get_docker_host_envs()); - exit_status = cmd.exec_with_abort( - Duration::minutes(BUILD_DURATION_TIMEOUT_MIN), - |line| { - info!("{}", line); + exit_status = cmd + .exec_with_abort( + Duration::minutes(BUILD_DURATION_TIMEOUT_MIN), + |line| { + self.logger.log( + LogLevel::Info, + EngineEvent::Info(self.get_event_details(), EventMessage::new_from_safe(line.to_string())), + ); - lh.deployment_in_progress(ProgressInfo::new( - ProgressScope::Application { - id: build.image.application_id.clone(), - }, - ProgressLevel::Info, - Some(line), - self.context.execution_id(), - )); - }, - |line| { - error!("{}", line); + lh.deployment_in_progress(ProgressInfo::new( + ProgressScope::Application { + id: build.image.application_id.clone(), + }, + ProgressLevel::Info, + Some(line), + self.context.execution_id(), + )); + }, + |line| { + self.logger.log( + LogLevel::Warning, + EngineEvent::Warning( + self.get_event_details(), + EventMessage::new_from_safe(line.to_string()), + ), + ); - lh.deployment_in_progress(ProgressInfo::new( - ProgressScope::Application { - id: build.image.application_id.clone(), - }, - ProgressLevel::Warn, - Some(line), - self.context.execution_id(), - )); - }, - is_task_canceled, - ); + lh.deployment_in_progress(ProgressInfo::new( + ProgressScope::Application { + id: build.image.application_id.clone(), + }, + ProgressLevel::Warn, + Some(line), + self.context.execution_id(), + )); + }, + is_task_canceled, + ) + .map_err(|err| CommandError::new(format!("{:?}", err), None)); if exit_status.is_ok() { // quit now if the builder successfully build the app @@ -310,21 +335,18 @@ impl LocalDocker { match exit_status { Ok(_) => Ok(BuildResult { build }), - Err(CommandError::Killed(msg)) => Err(self.engine_error(EngineErrorCause::Canceled, msg)), Err(err) => { - warn!("{:?}", err); + let error = EngineError::new_buildpack_cannot_build_container_image( + self.get_event_details(), + self.name_with_id(), + BUILDPACKS_BUILDERS.iter().map(|b| b.to_string()).collect(), + CommandError::new(format!("{:?}", err), None), + ); - Err(self.engine_error( - EngineErrorCause::User( - "None builders supports Your application can't be built without providing a Dockerfile", - ), - format!( - "Qovery can't build your container image {} with one of the following builders: {}. \ - Please do provide a valid Dockerfile to build your application or contact the support.", - self.name_with_id(), - BUILDPACKS_BUILDERS.join(", ") - ), - )) + self.logger + .log(LogLevel::Error, EngineEvent::Error(error.clone(), None)); + + Err(error) } } } @@ -335,7 +357,12 @@ impl LocalDocker { self.context.execution_id(), format!("build/{}", build.image.name.as_str()), ) - .map_err(|err| self.engine_error(EngineErrorCause::Internal, err.to_string())) + .map_err(|err| { + EngineError::new_cannot_get_workspace_directory( + self.get_event_details(), + CommandError::new(err.to_string(), None), + ) + }) } } @@ -358,11 +385,17 @@ impl BuildPlatform for LocalDocker { fn is_valid(&self) -> Result<(), EngineError> { if !crate::cmd::command::does_binary_exist("docker") { - return Err(self.engine_error(EngineErrorCause::Internal, String::from("docker binary not found"))); + return Err(EngineError::new_missing_required_binary( + self.get_event_details(), + "docker".to_string(), + )); } if !crate::cmd::command::does_binary_exist("pack") { - return Err(self.engine_error(EngineErrorCause::Internal, String::from("pack binary not found"))); + return Err(EngineError::new_missing_required_binary( + self.get_event_details(), + "pack".to_string(), + )); } Ok(()) @@ -374,9 +407,9 @@ impl BuildPlatform for LocalDocker { // Check if a local cache layers for the container image exists. let repository_root_path = self.get_repository_build_root_path(&build)?; - let parent_build = build - .to_previous_build(repository_root_path) - .map_err(|err| self.engine_error(EngineErrorCause::Internal, err.to_string()))?; + let parent_build = build.to_previous_build(repository_root_path).map_err(|err| { + EngineError::new_builder_get_build_error(self.get_event_details(), build.image.commit_id.to_string(), err) + })?; let parent_build = match parent_build { Some(parent_build) => parent_build, @@ -402,20 +435,32 @@ impl BuildPlatform for LocalDocker { force_build: bool, is_task_canceled: &dyn Fn() -> bool, ) -> Result { - info!("LocalDocker.build() called for {}", self.name()); + let event_details = self.get_event_details(); + + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe("LocalDocker.build() called".to_string()), + ), + ); + if is_task_canceled() { - return Err(self.engine_error( - EngineErrorCause::Canceled, - "Notified to cancel current task".to_string(), - )); + return Err(EngineError::new_task_cancellation_requested(event_details.clone())); } let listeners_helper = ListenersHelper::new(&self.listeners); if !force_build && self.image_does_exist(&build.image)? { - info!( - "image {:?} found on repository, container build is not required", - build.image + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(format!( + "Image `{}` found on repository, container build is not required", + build.image.name_with_tag() + )), + ), ); return Ok(BuildResult { build }); @@ -423,9 +468,15 @@ impl BuildPlatform for LocalDocker { let repository_root_path = self.get_repository_build_root_path(&build)?; - info!( - "cloning repository: {} to {}", - build.git_repository.url, repository_root_path + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(format!( + "Cloning repository: {} to {}", + build.git_repository.url, repository_root_path + )), + ), ); let get_credentials = |user: &str| { @@ -456,10 +507,7 @@ impl BuildPlatform for LocalDocker { // git clone if is_task_canceled() { - return Err(self.engine_error( - EngineErrorCause::Canceled, - "Notified to cancel current task".to_string(), - )); + return Err(EngineError::new_task_cancellation_requested(event_details.clone())); } if let Err(clone_error) = git::clone_at_commit( &build.git_repository.url, @@ -467,12 +515,16 @@ impl BuildPlatform for LocalDocker { &repository_root_path, &get_credentials, ) { - let message = format!( - "Error while cloning repository {}. Error: {:?}", - &build.git_repository.url, clone_error + let error = EngineError::new_builder_clone_repository_error( + self.get_event_details(), + build.git_repository.url.to_string(), + CommandError::new(clone_error.to_string(), None), ); - error!("{}", message); - return Err(self.engine_error(EngineErrorCause::Internal, message)); + + self.logger + .log(LogLevel::Error, EngineEvent::Error(error.clone(), None)); + + return Err(error); } let mut disable_build_cache = false; @@ -491,7 +543,15 @@ impl BuildPlatform for LocalDocker { // ensure docker_path is a mounted volume, otherwise ignore because it's not what Qovery does in production // ex: this cause regular cleanup on CI, leading to random tests errors match env::var_os("CI") { - Some(_) => info!("CI environment variable found, no docker prune will be made"), + Some(_) => self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe( + "CI environment variable found, no docker prune will be made".to_string(), + ), + ), + ), None => { // ensure there is enough disk space left before building a new image let docker_path_string = "/var/lib/docker"; @@ -503,9 +563,20 @@ impl BuildPlatform for LocalDocker { for disk in system.get_disks() { if disk.get_mount_point() == docker_path { - match check_docker_space_usage_and_clean(disk, self.get_docker_host_envs()) { - Ok(msg) => info!("{:?}", msg), - Err(e) => error!("{:?}", e.message), + let event_details = self.get_event_details(); + if let Err(e) = check_docker_space_usage_and_clean( + disk, + self.get_docker_host_envs(), + event_details.clone(), + &*self.logger(), + ) { + self.logger.log( + LogLevel::Warning, + EngineEvent::Warning( + event_details.clone(), + EventMessage::new(e.message_raw(), e.message_safe()), + ), + ); } break; }; @@ -530,7 +601,6 @@ impl BuildPlatform for LocalDocker { // If the dockerfile does not exist, abort if !Path::new(dockerfile_absolute_path.as_str()).exists() { - warn!("Dockerfile not found under {}", dockerfile_absolute_path); listeners_helper.error(ProgressInfo::new( ProgressScope::Application { id: build.image.application_id.clone(), @@ -543,14 +613,13 @@ impl BuildPlatform for LocalDocker { self.context.execution_id(), )); - return Err(self.engine_error( - EngineErrorCause::User("Dockerfile not found at location"), - format!( - "Your Dockerfile is not present at the specified location {}/{}", - build.git_repository.root_path.as_str(), - build.git_repository.dockerfile_path.unwrap_or_default().as_str() - ), - )); + let error = + EngineError::new_docker_cannot_find_dockerfile(self.get_event_details(), dockerfile_absolute_path); + + self.logger + .log(LogLevel::Error, EngineEvent::Error(error.clone(), None)); + + return Err(error); } self.build_image_with_docker( @@ -576,14 +645,14 @@ impl BuildPlatform for LocalDocker { let msg = match &result { Ok(_) => format!("✅ Container {} is built", self.name_with_id()), - Err(engine_err) if engine_err.is_cancel() => { + Err(engine_err) if engine_err.tag() == &Tag::TaskCancellationRequested => { format!("🚫 Container {} build has been canceled", self.name_with_id()) } Err(engine_err) => { format!( "❌ Container {} failed to be build: {}", self.name_with_id(), - engine_err.message.as_ref().map(|x| x.as_str()).unwrap_or_default() + engine_err.message() ) } }; @@ -591,15 +660,27 @@ impl BuildPlatform for LocalDocker { listeners_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Application { id: app_id }, ProgressLevel::Info, - Some(msg), + Some(msg.to_string()), self.context.execution_id(), )); + self.logger.log( + LogLevel::Info, + EngineEvent::Info(event_details.clone(), EventMessage::new_from_safe(msg.to_string())), + ); + result } fn build_error(&self, build: Build) -> Result { - warn!("LocalDocker.build_error() called for {}", self.name()); + let event_details = self.get_event_details(); + self.logger.log( + LogLevel::Warning, + EngineEvent::Warning( + event_details.clone(), + EventMessage::new_from_safe(format!("LocalDocker.build_error() called for {}", self.name())), + ), + ); let listener_helper = ListenersHelper::new(&self.listeners); @@ -615,8 +696,16 @@ impl BuildPlatform for LocalDocker { self.context.execution_id(), )); + let err = EngineError::new_not_implemented_error(event_details); + + self.logger.log(LogLevel::Error, EngineEvent::Error(err.clone(), None)); + // FIXME - Err(self.engine_error(EngineErrorCause::Internal, message)) + Err(err) + } + + fn logger(&self) -> Box { + self.logger.clone() } } @@ -630,38 +719,54 @@ impl Listen for LocalDocker { } } +impl ToTransmitter for LocalDocker { + fn to_transmitter(&self) -> Transmitter { + Transmitter::BuildPlatform(self.id().to_string(), self.name().to_string()) + } +} + fn check_docker_space_usage_and_clean( docker_path_size_info: &Disk, envs: Vec<(&str, &str)>, -) -> Result { + event_details: EventDetails, + logger: &dyn Logger, +) -> Result<(), CommandError> { let docker_max_disk_percentage_usage_before_purge = 60; // arbitrary percentage that should make the job anytime let available_space = docker_path_size_info.get_available_space(); let docker_percentage_remaining = available_space * 100 / docker_path_size_info.get_total_space(); if docker_percentage_remaining < docker_max_disk_percentage_usage_before_purge || available_space == 0 { - warn!( - "Docker disk remaining ({}%) is lower than {}%, requesting cleaning (purge)", - docker_percentage_remaining, docker_max_disk_percentage_usage_before_purge + logger.log( + LogLevel::Warning, + EngineEvent::Warning( + event_details.clone(), + EventMessage::new_from_safe(format!( + "Docker disk remaining ({}%) is lower than {}%, requesting cleaning (purge)", + docker_percentage_remaining, docker_max_disk_percentage_usage_before_purge + )), + ), ); - return match docker_prune_images(envs) { - Err(e) => { - error!("error while purging docker images: {:?}", e.message); - Err(e) - } - _ => Ok("docker images have been purged".to_string()), - }; + return docker_prune_images(envs); }; - Ok(format!( - "no need to purge old docker images, only {}% ({}/{}) disk used", - 100 - docker_percentage_remaining, - docker_path_size_info.get_available_space(), - docker_path_size_info.get_total_space(), - )) + logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(format!( + "No need to purge old docker images, only {}% ({}/{}) disk used", + 100 - docker_percentage_remaining, + docker_path_size_info.get_available_space(), + docker_path_size_info.get_total_space(), + )), + ), + ); + + Ok(()) } -fn docker_prune_images(envs: Vec<(&str, &str)>) -> Result<(), SimpleError> { +fn docker_prune_images(envs: Vec<(&str, &str)>) -> Result<(), CommandError> { let all_prunes_commands = vec![ vec!["container", "prune", "-f"], vec!["image", "prune", "-a", "-f"], @@ -669,20 +774,19 @@ fn docker_prune_images(envs: Vec<(&str, &str)>) -> Result<(), SimpleError> { vec!["volume", "prune", "-f"], ]; + let mut errored_commands = vec![]; for prune in all_prunes_commands { let mut cmd = QoveryCommand::new("docker", &prune, &envs); - match cmd.exec_with_timeout( - Duration::minutes(BUILD_DURATION_TIMEOUT_MIN), - |line| { - debug!("{}", line); - }, - |line| { - debug!("{}", line); - }, - ) { - Ok(_) => {} - Err(e) => error!("error while puring {}. {:?}", prune[0], e), - }; + if let Err(e) = cmd.exec_with_timeout(Duration::minutes(BUILD_DURATION_TIMEOUT_MIN), |_| {}, |_| {}) { + errored_commands.push(format!("{} {:?}", prune[0], e)); + } + } + + if errored_commands.len() > 0 { + return Err(CommandError::new( + errored_commands.join("/ "), + Some("Error while trying to prune images.".to_string()), + )); } Ok(()) diff --git a/src/build_platform/mod.rs b/src/build_platform/mod.rs index 4740b15a..f3273862 100644 --- a/src/build_platform/mod.rs +++ b/src/build_platform/mod.rs @@ -1,18 +1,20 @@ use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; -use crate::error::{EngineError, EngineErrorCause, EngineErrorScope}; +use crate::errors::{CommandError, EngineError}; +use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter}; use crate::git; -use crate::models::{Context, Listen}; +use crate::logger::Logger; +use crate::models::{Context, Listen, QoveryIdentifier}; use crate::utilities::get_image_tag; -use git2::{Cred, CredentialType, Error}; +use git2::{Cred, CredentialType}; use std::fmt::{Display, Formatter, Result as FmtResult}; use std::path::Path; pub mod docker; pub mod local_docker; -pub trait BuildPlatform: Listen { +pub trait BuildPlatform: ToTransmitter + Listen { fn context(&self) -> &Context; fn kind(&self) -> Kind; fn id(&self) -> &str; @@ -29,15 +31,17 @@ pub trait BuildPlatform: Listen { is_task_canceled: &dyn Fn() -> 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), + fn logger(&self) -> Box; + fn get_event_details(&self) -> EventDetails { + let context = self.context(); + EventDetails::new( + None, + QoveryIdentifier::from(context.organization_id().to_string()), + QoveryIdentifier::from(context.cluster_id().to_string()), + QoveryIdentifier::from(context.execution_id().to_string()), + None, + Stage::Environment(EnvironmentStep::Build), + self.to_transmitter(), ) } } @@ -49,7 +53,7 @@ pub struct Build { } impl Build { - pub fn to_previous_build

(&self, clone_repo_into_dir: P) -> Result, Error> + pub fn to_previous_build

(&self, clone_repo_into_dir: P) -> Result, CommandError> where P: AsRef, { @@ -64,7 +68,8 @@ impl Build { Cred::userpass_plaintext(creds.login.as_str(), creds.password.as_str()).unwrap(), )], }, - )?; + ) + .map_err(|err| CommandError::new(err.to_string(), Some("Cannot get parent commit ID.".to_string())))?; let parent_commit_id = match parent_commit_id { None => return Ok(None), diff --git a/src/cloud_provider/aws/application.rs b/src/cloud_provider/aws/application.rs index ad13ee66..7768f164 100644 --- a/src/cloud_provider/aws/application.rs +++ b/src/cloud_provider/aws/application.rs @@ -1,6 +1,7 @@ use tera::Context as TeraContext; use crate::build_platform::Image; +use crate::cloud_provider::kubernetes::validate_k8s_required_cpu_and_burstable; use crate::cloud_provider::models::{ EnvironmentVariable, EnvironmentVariableDataTemplate, Storage, StorageDataTemplate, }; @@ -9,13 +10,13 @@ use crate::cloud_provider::service::{ scale_down_application, send_progress_on_long_task, Action, Application as CApplication, Create, Delete, Helm, Pause, Service, ServiceType, StatelessService, }; -use crate::cloud_provider::utilities::{print_action, sanitize_name, validate_k8s_required_cpu_and_burstable}; +use crate::cloud_provider::utilities::{print_action, sanitize_name}; use crate::cloud_provider::DeploymentTarget; use crate::cmd::helm::Timeout; use crate::cmd::kubectl::ScalingKind::{Deployment, Statefulset}; -use crate::error::EngineErrorCause::Internal; -use crate::error::{EngineError, EngineErrorScope}; -use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter}; +use crate::errors::EngineError; +use crate::events::{EngineEvent, EnvironmentStep, EventMessage, Stage, ToTransmitter, Transmitter}; +use crate::logger::{LogLevel, Logger}; use crate::models::{Context, Listen, Listener, Listeners, ListenersHelper, Port}; use ::function_name::named; @@ -35,6 +36,7 @@ pub struct Application { storage: Vec>, environment_variables: Vec, listeners: Listeners, + logger: Box, } impl Application { @@ -54,6 +56,7 @@ impl Application { storage: Vec>, environment_variables: Vec, listeners: Listeners, + logger: Box, ) -> Self { Application { context, @@ -71,6 +74,7 @@ impl Application { storage, environment_variables, listeners, + logger, } } @@ -192,6 +196,7 @@ impl Service for Application { } fn tera_context(&self, target: &DeploymentTarget) -> Result { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration)); let mut context = default_tera_context(self, target.kubernetes, target.environment); let commit_id = self.image().commit_id.as_str(); @@ -201,10 +206,18 @@ impl Service for Application { Some(registry_url) => context.insert("image_name_with_tag", registry_url.as_str()), None => { let image_name_with_tag = self.image().name_with_tag(); - warn!( - "there is no registry url, use image name with tag with the default container registry: {}", - image_name_with_tag.as_str() + + self.logger().log( + LogLevel::Warning, + EngineEvent::Warning( + event_details.clone(), + EventMessage::new_from_safe(format!( + "there is no registry url, use image name with tag with the default container registry: {}", + image_name_with_tag.as_str() + )), + ), ); + context.insert("image_name_with_tag", image_name_with_tag.as_str()); } } @@ -237,17 +250,20 @@ impl Service for Application { &self.id, self.total_cpus(), self.cpu_burst(), + event_details.clone(), + self.logger(), ) { Ok(l) => l, Err(e) => { - return Err(EngineError::new( - Internal, - EngineErrorScope::Application(self.id().to_string(), self.name().to_string()), - self.context.execution_id(), - Some(e.to_string()), + return Err(EngineError::new_k8s_validate_required_cpu_and_burstable_error( + event_details.clone(), + self.total_cpus(), + self.cpu_burst(), + e, )); } }; + context.insert("cpu_burst", &cpu_limits.cpu_limit); let storage = self @@ -286,23 +302,26 @@ impl Service for Application { Ok(context) } - fn selector(&self) -> Option { - Some(format!("appId={}", self.id)) + fn logger(&self) -> &dyn Logger { + &*self.logger } - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::Application(self.id().to_string(), self.name().to_string()) + fn selector(&self) -> Option { + Some(format!("appId={}", self.id)) } } impl Create for Application { #[named] fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { deploy_user_stateless_service(target, self) @@ -315,11 +334,14 @@ impl Create for Application { #[named] fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { @@ -331,11 +353,14 @@ impl Create for Application { impl Pause for Application { #[named] fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || { @@ -354,11 +379,14 @@ impl Pause for Application { #[named] fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -374,6 +402,8 @@ impl Delete for Application { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || { @@ -393,6 +423,8 @@ impl Delete for Application { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || { diff --git a/src/cloud_provider/aws/databases/mongodb.rs b/src/cloud_provider/aws/databases/mongodb.rs index 1023915e..d3fb7bfb 100644 --- a/src/cloud_provider/aws/databases/mongodb.rs +++ b/src/cloud_provider/aws/databases/mongodb.rs @@ -6,7 +6,7 @@ use tera::Context as TeraContext; use crate::cloud_provider::service::{ check_service_version, default_tera_context, delete_stateful_service, deploy_stateful_service, get_tfstate_name, get_tfstate_suffix, scale_down_database, send_progress_on_long_task, Action, Create, Database, DatabaseOptions, - DatabaseType, Delete, Helm, Pause, Service, ServiceType, StatefulService, Terraform, + DatabaseType, Delete, Helm, Pause, Service, ServiceType, ServiceVersionCheckResult, StatefulService, Terraform, }; use crate::cloud_provider::utilities::{ generate_supported_version, get_self_hosted_mongodb_version, get_supported_version_to_use, print_action, @@ -14,8 +14,9 @@ use crate::cloud_provider::utilities::{ use crate::cloud_provider::DeploymentTarget; use crate::cmd::helm::Timeout; use crate::cmd::kubectl; -use crate::error::{EngineError, EngineErrorScope, StringError}; -use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter}; +use crate::errors::{CommandError, EngineError}; +use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter}; +use crate::logger::Logger; use crate::models::DatabaseMode::MANAGED; use crate::models::{Context, Listen, Listener, Listeners}; use ::function_name::named; @@ -33,6 +34,7 @@ pub struct MongoDB { database_instance_type: String, options: DatabaseOptions, listeners: Listeners, + logger: Box, } impl MongoDB { @@ -49,6 +51,7 @@ impl MongoDB { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: Box, ) -> Self { MongoDB { context, @@ -63,11 +66,21 @@ impl MongoDB { database_instance_type: database_instance_type.to_string(), options, listeners, + logger, } } - fn matching_correct_version(&self, is_managed_services: bool) -> Result { - check_service_version(get_mongodb_version(self.version(), is_managed_services), self) + fn matching_correct_version( + &self, + is_managed_services: bool, + event_details: EventDetails, + ) -> Result { + check_service_version( + get_mongodb_version(self.version(), is_managed_services), + self, + event_details, + self.logger(), + ) } fn cloud_provider_name(&self) -> &str { @@ -155,17 +168,14 @@ impl Service for MongoDB { } fn tera_context(&self, target: &DeploymentTarget) -> Result { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration)); let kubernetes = target.kubernetes; let environment = target.environment; let mut context = default_tera_context(self, target.kubernetes, target.environment); // we need the kubernetes config file to store tfstates file in kube secrets - let kube_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(path) => path, - Err(e) => { - return Err(e.to_legacy_engine_error()); - } - }; + let kube_config_file_path = kubernetes.get_kubeconfig_file_path()?; + context.insert("kubeconfig_path", &kube_config_file_path); kubectl::kubectl_exec_create_namespace_without_labels( @@ -176,7 +186,9 @@ impl Service for MongoDB { context.insert("namespace", environment.namespace()); - let version = self.matching_correct_version(self.is_managed_service())?; + let version = self + .matching_correct_version(self.is_managed_service(), event_details.clone())? + .matched_version(); context.insert("version", &version); for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() { @@ -221,16 +233,12 @@ impl Service for MongoDB { Ok(context) } - fn selector(&self) -> Option { - Some(format!("app={}", self.sanitized_name())) + fn logger(&self) -> &dyn Logger { + &*self.logger } - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::Database( - self.id().to_string(), - self.service_type().name().to_string(), - self.name().to_string(), - ) + fn selector(&self) -> Option { + Some(format!("app={}", self.sanitized_name())) } } @@ -287,24 +295,35 @@ impl Create for MongoDB { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { - deploy_stateful_service(target, self, event_details.clone()) + deploy_stateful_service(target, self, event_details.clone(), &*self.logger) }) } fn on_create_check(&self) -> Result<(), EngineError> { - self.check_domains(self.listeners.clone(), vec![self.fqdn.as_str()]) + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); + self.check_domains( + self.listeners.clone(), + vec![self.fqdn.as_str()], + event_details, + self.logger(), + ) } #[named] fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } @@ -313,11 +332,14 @@ impl Create for MongoDB { impl Pause for MongoDB { #[named] fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || { @@ -331,11 +353,14 @@ impl Pause for MongoDB { #[named] fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -351,10 +376,12 @@ impl Delete for MongoDB { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || { - delete_stateful_service(target, self, event_details.clone()) + delete_stateful_service(target, self, event_details.clone(), self.logger()) }) } @@ -364,11 +391,14 @@ impl Delete for MongoDB { #[named] fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } @@ -384,7 +414,7 @@ impl Listen for MongoDB { } } -fn get_mongodb_version(requested_version: String, is_managed_service: bool) -> Result { +fn get_mongodb_version(requested_version: String, is_managed_service: bool) -> Result { if is_managed_service { get_managed_mongodb_version(requested_version) } else { @@ -392,7 +422,7 @@ fn get_mongodb_version(requested_version: String, is_managed_service: bool) -> R } } -fn get_managed_mongodb_version(requested_version: String) -> Result { +fn get_managed_mongodb_version(requested_version: String) -> Result { let mut supported_mongodb_versions = HashMap::new(); // v3.6.0 @@ -410,6 +440,7 @@ fn get_managed_mongodb_version(requested_version: String) -> Result, } impl MySQL { @@ -50,6 +52,7 @@ impl MySQL { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: Box, ) -> Self { Self { context, @@ -64,11 +67,21 @@ impl MySQL { database_instance_type: database_instance_type.to_string(), options, listeners, + logger, } } - fn matching_correct_version(&self, is_managed_services: bool) -> Result { - check_service_version(get_mysql_version(self.version(), is_managed_services), self) + fn matching_correct_version( + &self, + is_managed_services: bool, + event_details: EventDetails, + ) -> Result { + check_service_version( + get_mysql_version(self.version(), is_managed_services), + self, + event_details, + self.logger(), + ) } fn cloud_provider_name(&self) -> &str { @@ -161,17 +174,13 @@ impl Service for MySQL { } fn tera_context(&self, target: &DeploymentTarget) -> Result { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration)); let kubernetes = target.kubernetes; let environment = target.environment; let mut context = default_tera_context(self, kubernetes, environment); // we need the kubernetes config file to store tfstates file in kube secrets - let kube_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(path) => path, - Err(e) => { - return Err(e.to_legacy_engine_error()); - } - }; + let kube_config_file_path = kubernetes.get_kubeconfig_file_path()?; context.insert("kubeconfig_path", &kube_config_file_path); kubectl::kubectl_exec_create_namespace_without_labels( @@ -182,21 +191,23 @@ impl Service for MySQL { context.insert("namespace", environment.namespace()); - let version = &self.matching_correct_version(self.is_managed_service())?; - context.insert("version", &version); + let version = &self.matching_correct_version(self.is_managed_service(), event_details.clone())?; + context.insert("version", &version.matched_version()); if self.is_managed_service() { - let parameter_group_family = match get_parameter_group_from_version(&version, DatabaseKind::Mysql) { - Ok(v) => v, - Err(e) => { - return Err(EngineError { - cause: EngineErrorCause::Internal, - scope: EngineErrorScope::Engine, - execution_id: (&self.context.execution_id()).to_string(), - message: Some(e), - }) - } - }; + let parameter_group_family = + match get_parameter_group_from_version(version.matched_version(), DatabaseKind::Mysql) { + Ok(v) => v, + Err(e) => { + return Err(EngineError::new_terraform_unsupported_context_parameter_value( + event_details.clone(), + "MySQL".to_string(), + "parameter_group_family".to_string(), + version.matched_version().to_string(), + Some(e), + )) + } + }; context.insert("parameter_group_family", ¶meter_group_family); }; @@ -242,16 +253,12 @@ impl Service for MySQL { Ok(context) } - fn selector(&self) -> Option { - Some(format!("app={}", self.sanitized_name())) + fn logger(&self) -> &dyn Logger { + &*self.logger } - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::Database( - self.id().to_string(), - self.service_type().name().to_string(), - self.name().to_string(), - ) + fn selector(&self) -> Option { + Some(format!("app={}", self.sanitized_name())) } } @@ -298,24 +305,35 @@ impl Create for MySQL { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { - deploy_stateful_service(target, self, event_details.clone()) + deploy_stateful_service(target, self, event_details.clone(), self.logger()) }) } fn on_create_check(&self) -> Result<(), EngineError> { - self.check_domains(self.listeners.clone(), vec![self.fqdn.as_str()]) + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); + self.check_domains( + self.listeners.clone(), + vec![self.fqdn.as_str()], + event_details, + self.logger(), + ) } #[named] fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -325,11 +343,14 @@ impl Create for MySQL { impl Pause for MySQL { #[named] fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || { @@ -343,11 +364,14 @@ impl Pause for MySQL { #[named] fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } @@ -362,10 +386,12 @@ impl Delete for MySQL { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || { - delete_stateful_service(target, self, event_details.clone()) + delete_stateful_service(target, self, event_details.clone(), self.logger()) }) } @@ -375,11 +401,14 @@ impl Delete for MySQL { #[named] fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } @@ -395,7 +424,7 @@ impl Listen for MySQL { } } -fn get_mysql_version(requested_version: String, is_managed_service: bool) -> Result { +fn get_mysql_version(requested_version: String, is_managed_service: bool) -> Result { if is_managed_service { get_managed_mysql_version(requested_version) } else { @@ -403,7 +432,7 @@ fn get_mysql_version(requested_version: String, is_managed_service: bool) -> Res } } -fn get_managed_mysql_version(requested_version: String) -> Result { +fn get_managed_mysql_version(requested_version: String) -> Result { let mut supported_mysql_versions = HashMap::new(); // https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_MySQL.html#MySQL.Concepts.VersionMgmt @@ -432,6 +461,7 @@ fn get_managed_mysql_version(requested_version: String) -> Result, } impl PostgreSQL { @@ -50,6 +52,7 @@ impl PostgreSQL { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: Box, ) -> Self { PostgreSQL { context, @@ -64,11 +67,21 @@ impl PostgreSQL { database_instance_type: database_instance_type.to_string(), options, listeners, + logger, } } - fn matching_correct_version(&self, is_managed_services: bool) -> Result { - check_service_version(get_postgres_version(self.version(), is_managed_services), self) + fn matching_correct_version( + &self, + is_managed_services: bool, + event_details: EventDetails, + ) -> Result { + check_service_version( + get_postgres_version(self.version(), is_managed_services), + self, + event_details, + self.logger(), + ) } fn cloud_provider_name(&self) -> &str { @@ -161,17 +174,13 @@ impl Service for PostgreSQL { } fn tera_context(&self, target: &DeploymentTarget) -> Result { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration)); let kubernetes = target.kubernetes; let environment = target.environment; let mut context = default_tera_context(self, kubernetes, environment); // we need the kubernetes config file to store tfstates file in kube secrets - let kube_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(path) => path, - Err(e) => { - return Err(e.to_legacy_engine_error()); - } - }; + let kube_config_file_path = kubernetes.get_kubeconfig_file_path()?; context.insert("kubeconfig_path", &kube_config_file_path); kubectl::kubectl_exec_create_namespace_without_labels( @@ -182,7 +191,9 @@ impl Service for PostgreSQL { context.insert("namespace", environment.namespace()); - let version = self.matching_correct_version(self.is_managed_service())?; + let version = self + .matching_correct_version(self.is_managed_service(), event_details.clone())? + .matched_version(); context.insert("version", &version); for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() { @@ -229,16 +240,12 @@ impl Service for PostgreSQL { Ok(context) } - fn selector(&self) -> Option { - Some(format!("app={}", self.sanitized_name())) + fn logger(&self) -> &dyn Logger { + &*self.logger } - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::Database( - self.id().to_string(), - self.service_type().name().to_string(), - self.name().to_string(), - ) + fn selector(&self) -> Option { + Some(format!("app={}", self.sanitized_name())) } } @@ -285,24 +292,35 @@ impl Create for PostgreSQL { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { - deploy_stateful_service(target, self, event_details.clone()) + deploy_stateful_service(target, self, event_details.clone(), self.logger()) }) } fn on_create_check(&self) -> Result<(), EngineError> { - self.check_domains(self.listeners.clone(), vec![self.fqdn.as_str()]) + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); + self.check_domains( + self.listeners.clone(), + vec![self.fqdn.as_str()], + event_details, + self.logger(), + ) } #[named] fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -312,11 +330,14 @@ impl Create for PostgreSQL { impl Pause for PostgreSQL { #[named] fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || { @@ -330,11 +351,14 @@ impl Pause for PostgreSQL { #[named] fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -350,10 +374,12 @@ impl Delete for PostgreSQL { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || { - delete_stateful_service(target, self, event_details.clone()) + delete_stateful_service(target, self, event_details.clone(), self.logger()) }) } @@ -363,11 +389,14 @@ impl Delete for PostgreSQL { #[named] fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -384,7 +413,7 @@ impl Listen for PostgreSQL { } } -fn get_postgres_version(requested_version: String, is_managed_service: bool) -> Result { +fn get_postgres_version(requested_version: String, is_managed_service: bool) -> Result { if is_managed_service { get_managed_postgres_version(requested_version) } else { @@ -392,7 +421,7 @@ fn get_postgres_version(requested_version: String, is_managed_service: bool) -> } } -fn get_managed_postgres_version(requested_version: String) -> Result { +fn get_managed_postgres_version(requested_version: String) -> Result { let mut supported_postgres_versions = HashMap::new(); // https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_PostgreSQL.html#PostgreSQL.Concepts @@ -423,6 +452,7 @@ fn get_managed_postgres_version(requested_version: String) -> Result, } impl Redis { @@ -47,6 +49,7 @@ impl Redis { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: Box, ) -> Self { Self { context, @@ -61,11 +64,21 @@ impl Redis { database_instance_type: database_instance_type.to_string(), options, listeners, + logger, } } - fn matching_correct_version(&self, is_managed_services: bool) -> Result { - check_service_version(get_redis_version(self.version(), is_managed_services), self) + fn matching_correct_version( + &self, + is_managed_services: bool, + event_details: EventDetails, + ) -> Result { + check_service_version( + get_redis_version(self.version(), is_managed_services), + self, + event_details, + self.logger(), + ) } fn cloud_provider_name(&self) -> &str { @@ -164,17 +177,14 @@ impl Service for Redis { } fn tera_context(&self, target: &DeploymentTarget) -> Result { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration)); let kubernetes = target.kubernetes; let environment = target.environment; let mut context = default_tera_context(self, kubernetes, environment); // we need the kubernetes config file to store tfstates file in kube secrets - let kube_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(path) => path, - Err(e) => { - return Err(e.to_legacy_engine_error()); - } - }; + let kube_config_file_path = kubernetes.get_kubeconfig_file_path()?; + context.insert("kubeconfig_path", &kube_config_file_path); kubectl::kubectl_exec_create_namespace_without_labels( @@ -183,23 +193,29 @@ impl Service for Redis { kubernetes.cloud_provider().credentials_environment_variables(), ); - let version = self.matching_correct_version(self.is_managed_service())?; + let version = self + .matching_correct_version(self.is_managed_service(), event_details.clone())? + .matched_version() + .to_string(); let parameter_group_name = if version.starts_with("5.") { "default.redis5.0" } else if version.starts_with("6.") { "default.redis6.x" } else { - return Err(self.engine_error( - EngineErrorCause::Internal, - "Elasticache parameter group name unknown".to_string(), + return Err(EngineError::new_terraform_unsupported_context_parameter_value( + event_details.clone(), + "Elasicache".to_string(), + "database_elasticache_parameter_group_name".to_string(), + format!("default.redis{}", version), + None, )); }; context.insert("database_elasticache_parameter_group_name", parameter_group_name); context.insert("namespace", environment.namespace()); - context.insert("version", &version); + context.insert("version", version.as_str()); for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() { context.insert(k, v); @@ -241,16 +257,12 @@ impl Service for Redis { Ok(context) } - fn selector(&self) -> Option { - Some(format!("app={}", self.sanitized_name())) + fn logger(&self) -> &dyn Logger { + &*self.logger } - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::Database( - self.id().to_string(), - self.service_type().name().to_string(), - self.name().to_string(), - ) + fn selector(&self) -> Option { + Some(format!("app={}", self.sanitized_name())) } } @@ -297,24 +309,35 @@ impl Create for Redis { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { - deploy_stateful_service(target, self, event_details.clone()) + deploy_stateful_service(target, self, event_details.clone(), self.logger()) }) } fn on_create_check(&self) -> Result<(), EngineError> { - self.check_domains(self.listeners.clone(), vec![self.fqdn.as_str()]) + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); + self.check_domains( + self.listeners.clone(), + vec![self.fqdn.as_str()], + event_details, + self.logger(), + ) } #[named] fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } @@ -323,11 +346,14 @@ impl Create for Redis { impl Pause for Redis { #[named] fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || { @@ -341,11 +367,14 @@ impl Pause for Redis { #[named] fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -361,10 +390,12 @@ impl Delete for Redis { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || { - delete_stateful_service(target, self, event_details.clone()) + delete_stateful_service(target, self, event_details.clone(), self.logger()) }) } @@ -374,11 +405,14 @@ impl Delete for Redis { #[named] fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } @@ -394,7 +428,7 @@ impl Listen for Redis { } } -fn get_redis_version(requested_version: String, is_managed_service: bool) -> Result { +fn get_redis_version(requested_version: String, is_managed_service: bool) -> Result { if is_managed_service { get_managed_redis_version(requested_version) } else { @@ -402,7 +436,7 @@ fn get_redis_version(requested_version: String, is_managed_service: bool) -> Res } } -fn get_managed_redis_version(requested_version: String) -> Result { +fn get_managed_redis_version(requested_version: String) -> Result { let mut supported_redis_versions = HashMap::with_capacity(2); // https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/supported-engine-versions.html @@ -416,6 +450,7 @@ fn get_managed_redis_version(requested_version: String) -> Result Result { - let version_number = match VersionsNumber::from_str(version) { - Ok(v) => { - if v.minor.is_none() { - return Err(format!( - "Can't determine the minor version, to select parameter group for {:?} version {}", - database_kind, version - )); - }; - v - } - Err(e) => return Err(e), +pub fn get_parameter_group_from_version( + version: VersionsNumber, + database_kind: DatabaseKind, +) -> Result { + if version.minor.is_none() { + return Err(CommandError::new_from_safe_message(format!( + "Can't determine the minor version, to select parameter group for {:?} version {}", + database_kind, version + ))); }; match database_kind { - DatabaseKind::Mysql => Ok(format!( - "mysql{}.{}", - version_number.major, - version_number.minor.unwrap() - )), + DatabaseKind::Mysql => Ok(format!("mysql{}.{}", version.major, version.minor.unwrap())), _ => Ok("".to_string()), } } @@ -35,19 +27,30 @@ pub fn aws_final_snapshot_name(database_name: &str) -> String { #[cfg(test)] mod tests_aws_databases_parameters { use crate::cloud_provider::aws::databases::utilities::get_parameter_group_from_version; + use crate::cloud_provider::utilities::VersionsNumber; use crate::models::DatabaseKind; + use std::str::FromStr; #[test] fn check_rds_mysql_parameter_groups() { - let mysql_parameter_group = get_parameter_group_from_version("5.7.0", DatabaseKind::Mysql); + let mysql_parameter_group = get_parameter_group_from_version( + VersionsNumber::from_str("5.7.0").expect("error while trying to get version from str"), + DatabaseKind::Mysql, + ); assert_eq!(mysql_parameter_group.unwrap(), "mysql5.7"); - let mysql_parameter_group = get_parameter_group_from_version("8.0", DatabaseKind::Mysql); + let mysql_parameter_group = get_parameter_group_from_version( + VersionsNumber::from_str("8.0").expect("error while trying to get version from str"), + DatabaseKind::Mysql, + ); assert_eq!(mysql_parameter_group.unwrap(), "mysql8.0"); - let mysql_parameter_group = get_parameter_group_from_version("8", DatabaseKind::Mysql); + let mysql_parameter_group = get_parameter_group_from_version( + VersionsNumber::from_str("8").expect("error while trying to get version from str"), + DatabaseKind::Mysql, + ); assert_eq!( - mysql_parameter_group.unwrap_err(), + mysql_parameter_group.unwrap_err().message(), "Can't determine the minor version, to select parameter group for Mysql version 8" ); } diff --git a/src/cloud_provider/aws/kubernetes/mod.rs b/src/cloud_provider/aws/kubernetes/mod.rs index d4a4dd86..9a71de3d 100644 --- a/src/cloud_provider/aws/kubernetes/mod.rs +++ b/src/cloud_provider/aws/kubernetes/mod.rs @@ -185,7 +185,7 @@ impl<'a> EKS<'a> { e, ); - logger.log(LogLevel::Error, EngineEvent::Error(err.clone())); + logger.log(LogLevel::Error, EngineEvent::Error(err.clone(), None)); return Err(err); } @@ -465,10 +465,13 @@ impl<'a> EKS<'a> { Some(secret_id) => context.insert("vault_secret_id", secret_id.to_str().unwrap()), None => self.logger().log( LogLevel::Error, - EngineEvent::Error(EngineError::new_missing_required_env_variable( - event_details.clone(), - "VAULT_SECRET_ID".to_string(), - )), + EngineEvent::Error( + EngineError::new_missing_required_env_variable( + event_details.clone(), + "VAULT_SECRET_ID".to_string(), + ), + None, + ), ), } } @@ -618,7 +621,7 @@ impl<'a> EKS<'a> { ) } Err(e) => { - self.logger().log(LogLevel::Error, EngineEvent::Error(e)); + self.logger().log(LogLevel::Error, EngineEvent::Error(e, None)); self.logger().log( LogLevel::Info, EngineEvent::Deploying( @@ -653,11 +656,10 @@ impl<'a> EKS<'a> { ), Err(e) => self.logger().log( LogLevel::Error, - EngineEvent::Error(EngineError::new_cannot_get_or_create_iam_role( - event_details.clone(), - role.role_name, - e, - )), + EngineEvent::Error( + EngineError::new_cannot_get_or_create_iam_role(event_details.clone(), role.role_name, e), + None, + ), ), } } @@ -736,10 +738,10 @@ impl<'a> EKS<'a> { } Err(e) => self.logger().log( LogLevel::Warning, - EngineEvent::Error(EngineError::new_terraform_state_does_not_exist( - event_details.clone(), - e, - )), + EngineEvent::Error( + EngineError::new_terraform_state_does_not_exist(event_details.clone(), e), + None, + ), ), }; @@ -941,7 +943,8 @@ impl<'a> EKS<'a> { } Err(e) => { let error = EngineError::new_terraform_state_does_not_exist(event_details.clone(), e); - self.logger().log(LogLevel::Error, EngineEvent::Error(error.clone())); + self.logger() + .log(LogLevel::Error, EngineEvent::Error(error.clone(), None)); return Err(error); } }; @@ -1149,10 +1152,10 @@ impl<'a> EKS<'a> { // An issue occurred during the apply before destroy of Terraform, it may be expected if you're resuming a destroy self.logger().log( LogLevel::Error, - EngineEvent::Error(EngineError::new_terraform_error_while_executing_pipeline( - event_details.clone(), - e, - )), + EngineEvent::Error( + EngineError::new_terraform_error_while_executing_pipeline(event_details.clone(), e), + None, + ), ); }; @@ -1509,22 +1512,28 @@ impl<'a> Kubernetes for EKS<'a> { #[named] fn on_create(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Create)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Create, || self.create()) } #[named] fn on_create_error(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Create)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Create, || self.create_error()) } @@ -1688,7 +1697,7 @@ impl<'a> Kubernetes for EKS<'a> { self.cloud_provider().credentials_environment_variables(), Stage::Infrastructure(InfrastructureStep::Upgrade), ) { - self.logger().log(LogLevel::Error, EngineEvent::Error(e.clone())); + self.logger().log(LogLevel::Error, EngineEvent::Error(e.clone(), None)); return Err(e); } @@ -1798,88 +1807,112 @@ impl<'a> Kubernetes for EKS<'a> { #[named] fn on_upgrade(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Upgrade)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Create, || self.upgrade()) } #[named] fn on_upgrade_error(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Upgrade)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Create, || self.upgrade_error()) } #[named] fn on_downgrade(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Downgrade)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Create, || self.downgrade()) } #[named] fn on_downgrade_error(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Downgrade)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Create, || self.downgrade_error()) } #[named] fn on_pause(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Pause, || self.pause()) } #[named] fn on_pause_error(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Pause, || self.pause_error()) } #[named] fn on_delete(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Delete, || self.delete()) } #[named] fn on_delete_error(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Delete, || self.delete_error()) } @@ -1892,8 +1925,10 @@ impl<'a> Kubernetes for EKS<'a> { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); - kubernetes::deploy_environment(self, environment, event_details) + kubernetes::deploy_environment(self, environment, event_details, self.logger()) } #[named] @@ -1904,8 +1939,10 @@ impl<'a> Kubernetes for EKS<'a> { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); - kubernetes::deploy_environment_error(self, environment, event_details) + kubernetes::deploy_environment_error(self, environment, event_details, self.logger()) } #[named] @@ -1916,17 +1953,22 @@ impl<'a> Kubernetes for EKS<'a> { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); - kubernetes::pause_environment(self, environment, event_details) + kubernetes::pause_environment(self, environment, event_details, self.logger()) } #[named] fn pause_environment_error(&self, _environment: &Environment) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); Ok(()) } @@ -1939,17 +1981,22 @@ impl<'a> Kubernetes for EKS<'a> { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); - kubernetes::delete_environment(self, environment, event_details) + kubernetes::delete_environment(self, environment, event_details, self.logger()) } #[named] fn delete_environment_error(&self, _environment: &Environment) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); Ok(()) } diff --git a/src/cloud_provider/aws/mod.rs b/src/cloud_provider/aws/mod.rs index 1c8ec9d0..e0f7e2ea 100644 --- a/src/cloud_provider/aws/mod.rs +++ b/src/cloud_provider/aws/mod.rs @@ -5,10 +5,11 @@ use rusoto_credential::StaticProvider; use rusoto_sts::{GetCallerIdentityRequest, Sts, StsClient}; use uuid::Uuid; -use crate::cloud_provider::{CloudProvider, EngineError, Kind, TerraformStateCredentials}; +use crate::cloud_provider::{CloudProvider, Kind, TerraformStateCredentials}; use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY}; -use crate::error::EngineErrorCause; -use crate::models::{Context, Listen, Listener, Listeners}; +use crate::errors::EngineError; +use crate::events::{EventDetails, GeneralStep, Stage, ToTransmitter, Transmitter}; +use crate::models::{Context, Listen, Listener, Listeners, QoveryIdentifier}; use crate::runtime::block_on; pub mod application; @@ -108,18 +109,15 @@ impl CloudProvider for AWS { } fn is_valid(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::General(GeneralStep::RetrieveClusterConfig)); let client = StsClient::new_with_client(self.client(), Region::default()); let s = block_on(client.get_caller_identity(GetCallerIdentityRequest::default())); match s { Ok(_x) => Ok(()), 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()), + return Err(EngineError::new_client_invalid_cloud_provider_credentials( + event_details, )); } } @@ -150,6 +148,19 @@ impl CloudProvider for AWS { fn as_any(&self) -> &dyn Any { self } + + fn get_event_details(&self, stage: Stage) -> EventDetails { + let context = self.context(); + EventDetails::new( + None, + QoveryIdentifier::from(context.organization_id().to_string()), + QoveryIdentifier::from(context.cluster_id().to_string()), + QoveryIdentifier::from(context.execution_id().to_string()), + None, + stage, + self.to_transmitter(), + ) + } } impl Listen for AWS { @@ -161,3 +172,9 @@ impl Listen for AWS { self.listeners.push(listener); } } + +impl ToTransmitter for AWS { + fn to_transmitter(&self) -> Transmitter { + Transmitter::CloudProvider(self.id.to_string(), self.name.to_string()) + } +} diff --git a/src/cloud_provider/aws/router.rs b/src/cloud_provider/aws/router.rs index 47dfdfc7..fd4aada7 100644 --- a/src/cloud_provider/aws/router.rs +++ b/src/cloud_provider/aws/router.rs @@ -10,9 +10,9 @@ use crate::cloud_provider::utilities::{check_cname_for, print_action, sanitize_n use crate::cloud_provider::DeploymentTarget; use crate::cmd::helm; use crate::cmd::helm::{to_engine_error, Timeout}; -use crate::error::{EngineError, EngineErrorScope}; -use crate::errors::EngineError as NewEngineError; -use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter}; +use crate::errors::EngineError; +use crate::events::{EngineEvent, EnvironmentStep, EventMessage, Stage, ToTransmitter, Transmitter}; +use crate::logger::{LogLevel, Logger}; use crate::models::{Context, Listen, Listener, Listeners}; use ::function_name::named; @@ -26,6 +26,7 @@ pub struct Router { sticky_sessions_enabled: bool, routes: Vec, listeners: Listeners, + logger: Box, } impl Router { @@ -39,6 +40,7 @@ impl Router { routes: Vec, sticky_sessions_enabled: bool, listeners: Listeners, + logger: Box, ) -> Self { Router { context, @@ -50,6 +52,7 @@ impl Router { sticky_sessions_enabled, routes, listeners, + logger, } } @@ -124,6 +127,7 @@ impl Service for Router { } fn tera_context(&self, target: &DeploymentTarget) -> Result { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration)); let kubernetes = target.kubernetes; let environment = target.environment; let mut context = default_tera_context(self, kubernetes, environment); @@ -177,10 +181,7 @@ impl Service for Router { context.insert("nginx_limit_cpu", "200m"); context.insert("nginx_limit_memory", "128Mi"); - let kubernetes_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(path) => path, - Err(e) => return Err(e.to_legacy_engine_error()), - }; + let kubernetes_config_file_path = kubernetes.get_kubeconfig_file_path()?; // Default domain match crate::cmd::kubectl::kubectl_exec_get_external_ingress_hostname( @@ -192,12 +193,28 @@ impl Service for Router { Ok(external_ingress_hostname_default) => match external_ingress_hostname_default { Some(hostname) => context.insert("external_ingress_hostname_default", hostname.as_str()), None => { - warn!("unable to get external_ingress_hostname_default - what's wrong? This must never happened"); + // TODO(benjaminch): Handle better this one via a proper error eventually + self.logger().log( + LogLevel::Warning, + EngineEvent::Warning( + event_details.clone(), + EventMessage::new_from_safe( + "Error while trying to get Load Balancer hostname from Kubernetes cluster".to_string(), + ), + ), + ); } }, _ => { // FIXME really? - warn!("can't fetch kubernetes config file - what's wrong? This must never happened"); + // TODO(benjaminch): Handle better this one via a proper error eventually + self.logger().log( + LogLevel::Warning, + EngineEvent::Warning( + event_details.clone(), + EventMessage::new_from_safe("Can't fetch external ingress hostname.".to_string()), + ), + ); } } @@ -224,12 +241,12 @@ impl Service for Router { Ok(context) } - fn selector(&self) -> Option { - Some(format!("routerId={}", self.id)) + fn logger(&self) -> &dyn Logger { + &*self.logger } - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::Router(self.id().to_string(), self.name().to_string()) + fn selector(&self) -> Option { + Some(format!("routerId={}", self.id)) } } @@ -292,22 +309,21 @@ impl ToTransmitter for Router { impl Create for Router { #[named] fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); - let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); let kubernetes = target.kubernetes; let environment = target.environment; let workspace_dir = self.workspace_directory(); let helm_release_name = self.helm_release_name(); - let kubernetes_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(p) => p, - Err(e) => return Err(e.to_legacy_engine_error()), - }; + let kubernetes_config_file_path = kubernetes.get_kubeconfig_file_path()?; // respect order - getting the context here and not before is mandatory // the nginx-ingress must be available to get the external dns target if necessary @@ -317,13 +333,12 @@ impl Create for Router { if let Err(e) = crate::template::generate_and_copy_all_files_into_dir(from_dir.as_str(), workspace_dir.as_str(), context) { - return Err(NewEngineError::new_cannot_copy_files_from_one_directory_to_another( + return Err(EngineError::new_cannot_copy_files_from_one_directory_to_another( event_details.clone(), from_dir.to_string(), workspace_dir.to_string(), e, - ) - .to_legacy_engine_error()); + )); } // do exec helm upgrade and return the last deployment status @@ -331,7 +346,8 @@ impl Create for Router { &kubernetes_config_file_path, &kubernetes.cloud_provider().credentials_environment_variables(), ) - .map_err(|e| to_engine_error(&event_details, e).to_legacy_engine_error())?; + .map_err(|e| to_engine_error(&event_details, e))?; + let chart = ChartInfo::new_from_custom_namespace( helm_release_name, workspace_dir.clone(), @@ -346,12 +362,14 @@ impl Create for Router { ); helm.upgrade(&chart, &vec![]) - .map_err(|e| NewEngineError::new_helm_error(event_details.clone(), e).to_legacy_engine_error()) + .map_err(|e| EngineError::new_helm_error(event_details.clone(), e)) } fn on_create_check(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); + // check non custom domains - self.check_domains()?; + self.check_domains(event_details.clone(), self.logger())?; // Wait/Check that custom domain is a CNAME targeting qovery for domain_to_check in self.custom_domains.iter() { @@ -365,9 +383,19 @@ impl Create for Router { continue } Ok(err) | Err(err) => { - warn!( - "Invalid CNAME for {}. Might not be an issue if user is using a CDN: {}", - domain_to_check.domain, err + // TODO(benjaminch): Handle better this one via a proper error eventually + self.logger().log( + LogLevel::Warning, + EngineEvent::Warning( + event_details.clone(), + EventMessage::new( + format!( + "Invalid CNAME for {}. Might not be an issue if user is using a CDN.", + domain_to_check.domain, + ), + Some(err.to_string()), + ), + ), ); } } @@ -378,11 +406,14 @@ impl Create for Router { #[named] fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { @@ -392,8 +423,17 @@ impl Create for Router { } impl Pause for Router { + #[named] fn on_pause(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { - info!("AWS.router.on_pause() called for {}, doing nothing", self.name()); + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); + print_action( + self.cloud_provider_name(), + self.struct_name(), + function_name!(), + self.name(), + event_details, + self.logger(), + ); Ok(()) } @@ -403,11 +443,14 @@ impl Pause for Router { #[named] fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } @@ -422,6 +465,8 @@ impl Delete for Router { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); delete_router(target, self, false, event_details) } @@ -438,6 +483,8 @@ impl Delete for Router { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); delete_router(target, self, true, event_details) } diff --git a/src/cloud_provider/digitalocean/application.rs b/src/cloud_provider/digitalocean/application.rs index 83560cf6..b7dd0d2b 100644 --- a/src/cloud_provider/digitalocean/application.rs +++ b/src/cloud_provider/digitalocean/application.rs @@ -1,6 +1,7 @@ use tera::Context as TeraContext; use crate::build_platform::Image; +use crate::cloud_provider::kubernetes::validate_k8s_required_cpu_and_burstable; use crate::cloud_provider::models::{ EnvironmentVariable, EnvironmentVariableDataTemplate, Storage, StorageDataTemplate, }; @@ -9,14 +10,13 @@ use crate::cloud_provider::service::{ scale_down_application, send_progress_on_long_task, Action, Create, Delete, Helm, Pause, Service, ServiceType, StatelessService, }; -use crate::cloud_provider::utilities::{print_action, sanitize_name, validate_k8s_required_cpu_and_burstable}; +use crate::cloud_provider::utilities::{print_action, sanitize_name}; use crate::cloud_provider::DeploymentTarget; use crate::cmd::helm::Timeout; use crate::cmd::kubectl::ScalingKind::{Deployment, Statefulset}; -use crate::error::EngineErrorCause::Internal; -use crate::error::{EngineError, EngineErrorScope}; -use crate::errors::CommandError; -use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter}; +use crate::errors::{CommandError, EngineError}; +use crate::events::{EngineEvent, EnvironmentStep, EventMessage, Stage, ToTransmitter, Transmitter}; +use crate::logger::{LogLevel, Logger}; use crate::models::{Context, Listen, Listener, Listeners, ListenersHelper, Port}; use ::function_name::named; use std::fmt; @@ -38,6 +38,7 @@ pub struct Application { storage: Vec>, environment_variables: Vec, listeners: Listeners, + logger: Box, } impl Application { @@ -57,6 +58,7 @@ impl Application { storage: Vec>, environment_variables: Vec, listeners: Listeners, + logger: Box, ) -> Self { Application { context, @@ -74,6 +76,7 @@ impl Application { storage, environment_variables, listeners, + logger, } } @@ -195,6 +198,7 @@ impl Service for Application { } fn tera_context(&self, target: &DeploymentTarget) -> Result { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration)); let kubernetes = target.kubernetes; let environment = target.environment; let mut context = default_tera_context(self, kubernetes, environment); @@ -206,10 +210,18 @@ impl Service for Application { Some(registry_url) => context.insert("image_name_with_tag", registry_url.as_str()), None => { let image_name_with_tag = self.image.name_with_tag(); - warn!( - "there is no registry url, use image name with tag with the default container registry: {}", - image_name_with_tag.as_str() + + self.logger().log( + LogLevel::Warning, + EngineEvent::Warning( + event_details.clone(), + EventMessage::new_from_safe(format!( + "there is no registry url, use image name with tag with the default container registry: {}", + image_name_with_tag.as_str() + )), + ), ); + context.insert("image_name_with_tag", image_name_with_tag.as_str()); } } @@ -220,14 +232,16 @@ impl Service for Application { &self.id, self.total_cpus(), self.cpu_burst(), + event_details.clone(), + self.logger(), ) { Ok(l) => l, Err(e) => { - return Err(EngineError::new( - Internal, - EngineErrorScope::Application(self.id().to_string(), self.name().to_string()), - self.context.execution_id(), - Some(e.to_string()), + return Err(EngineError::new_k8s_validate_required_cpu_and_burstable_error( + event_details.clone(), + self.total_cpus(), + self.cpu_burst(), + e, )); } }; @@ -288,23 +302,26 @@ impl Service for Application { Ok(context) } - fn selector(&self) -> Option { - Some(format!("appId={}", self.id)) + fn logger(&self) -> &dyn Logger { + &*self.logger } - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::Application(self.id().to_string(), self.name().to_string()) + fn selector(&self) -> Option { + Some(format!("appId={}", self.id)) } } impl Create for Application { #[named] fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { @@ -318,11 +335,14 @@ impl Create for Application { #[named] fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { @@ -334,11 +354,14 @@ impl Create for Application { impl Pause for Application { #[named] fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || { @@ -357,11 +380,14 @@ impl Pause for Application { #[named] fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -377,6 +403,8 @@ impl Delete for Application { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || { @@ -396,6 +424,8 @@ impl Delete for Application { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || { diff --git a/src/cloud_provider/digitalocean/databases/mongodb.rs b/src/cloud_provider/digitalocean/databases/mongodb.rs index 730d9e4d..e05c3138 100644 --- a/src/cloud_provider/digitalocean/databases/mongodb.rs +++ b/src/cloud_provider/digitalocean/databases/mongodb.rs @@ -3,14 +3,15 @@ use tera::Context as TeraContext; use crate::cloud_provider::service::{ check_service_version, default_tera_context, delete_stateful_service, deploy_stateful_service, get_tfstate_name, get_tfstate_suffix, scale_down_database, send_progress_on_long_task, Action, Create, Database, DatabaseOptions, - DatabaseType, Delete, Helm, Pause, Service, ServiceType, StatefulService, Terraform, + DatabaseType, Delete, Helm, Pause, Service, ServiceType, ServiceVersionCheckResult, StatefulService, Terraform, }; use crate::cloud_provider::utilities::{get_self_hosted_mongodb_version, print_action, sanitize_name}; use crate::cloud_provider::DeploymentTarget; use crate::cmd::helm::Timeout; use crate::cmd::kubectl; -use crate::error::{EngineError, EngineErrorScope}; -use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter}; +use crate::errors::EngineError; +use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter}; +use crate::logger::Logger; use crate::models::DatabaseMode::MANAGED; use crate::models::{Context, Listen, Listener, Listeners}; use ::function_name::named; @@ -28,6 +29,7 @@ pub struct MongoDB { database_instance_type: String, options: DatabaseOptions, listeners: Listeners, + logger: Box, } impl MongoDB { @@ -44,6 +46,7 @@ impl MongoDB { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: Box, ) -> Self { MongoDB { context, @@ -58,11 +61,17 @@ impl MongoDB { database_instance_type: database_instance_type.to_string(), options, listeners, + logger, } } - fn matching_correct_version(&self) -> Result { - check_service_version(get_self_hosted_mongodb_version(self.version().clone()), self) + fn matching_correct_version(&self, event_details: EventDetails) -> Result { + check_service_version( + get_self_hosted_mongodb_version(self.version().clone()), + self, + event_details, + self.logger(), + ) } fn cloud_provider_name(&self) -> &str { @@ -152,17 +161,13 @@ impl Service for MongoDB { } fn tera_context(&self, target: &DeploymentTarget) -> Result { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration)); let kubernetes = target.kubernetes; let environment = target.environment; let mut context = default_tera_context(self, kubernetes, environment); // we need the kubernetes config file to store tfstates file in kube secrets - let kube_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(path) => path, - Err(e) => { - return Err(e.to_legacy_engine_error()); - } - }; + let kube_config_file_path = kubernetes.get_kubeconfig_file_path()?; context.insert("kubeconfig_path", &kube_config_file_path); kubectl::kubectl_exec_create_namespace_without_labels( @@ -173,7 +178,7 @@ impl Service for MongoDB { context.insert("namespace", environment.namespace()); - let version = self.matching_correct_version()?; + let version = self.matching_correct_version(event_details.clone())?.matched_version(); context.insert("version", &version); for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() { @@ -218,12 +223,8 @@ impl Service for MongoDB { Some(format!("app={}", self.sanitized_name())) } - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::Database( - self.id().to_string(), - self.service_type().name().to_string(), - self.name().to_string(), - ) + fn logger(&self) -> &dyn Logger { + &*self.logger } } @@ -270,10 +271,12 @@ impl Create for MongoDB { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { - deploy_stateful_service(target, self, event_details.clone()) + deploy_stateful_service(target, self, event_details.clone(), self.logger()) }) } @@ -283,11 +286,14 @@ impl Create for MongoDB { #[named] fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -297,11 +303,14 @@ impl Create for MongoDB { impl Pause for MongoDB { #[named] fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || { @@ -315,11 +324,14 @@ impl Pause for MongoDB { #[named] fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -335,10 +347,12 @@ impl Delete for MongoDB { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || { - delete_stateful_service(target, self, event_details.clone()) + delete_stateful_service(target, self, event_details.clone(), self.logger()) }) } @@ -348,11 +362,14 @@ impl Delete for MongoDB { #[named] fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) diff --git a/src/cloud_provider/digitalocean/databases/mysql.rs b/src/cloud_provider/digitalocean/databases/mysql.rs index a43d54b0..651914f3 100644 --- a/src/cloud_provider/digitalocean/databases/mysql.rs +++ b/src/cloud_provider/digitalocean/databases/mysql.rs @@ -3,14 +3,15 @@ use tera::Context as TeraContext; use crate::cloud_provider::service::{ check_service_version, default_tera_context, delete_stateful_service, deploy_stateful_service, get_tfstate_name, get_tfstate_suffix, scale_down_database, send_progress_on_long_task, Action, Create, Database, DatabaseOptions, - DatabaseType, Delete, Helm, Pause, Service, ServiceType, StatefulService, Terraform, + DatabaseType, Delete, Helm, Pause, Service, ServiceType, ServiceVersionCheckResult, StatefulService, Terraform, }; use crate::cloud_provider::utilities::{get_self_hosted_mysql_version, print_action, sanitize_name}; use crate::cloud_provider::DeploymentTarget; use crate::cmd::helm::Timeout; use crate::cmd::kubectl; -use crate::error::{EngineError, EngineErrorScope}; -use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter}; +use crate::errors::EngineError; +use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter}; +use crate::logger::Logger; use crate::models::DatabaseMode::MANAGED; use crate::models::{Context, Listen, Listener, Listeners}; use ::function_name::named; @@ -28,6 +29,7 @@ pub struct MySQL { database_instance_type: String, options: DatabaseOptions, listeners: Listeners, + logger: Box, } impl MySQL { @@ -44,6 +46,7 @@ impl MySQL { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: Box, ) -> Self { Self { context, @@ -58,11 +61,17 @@ impl MySQL { database_instance_type: database_instance_type.to_string(), options, listeners, + logger, } } - fn matching_correct_version(&self) -> Result { - check_service_version(get_self_hosted_mysql_version(self.version()), self) + fn matching_correct_version(&self, event_details: EventDetails) -> Result { + check_service_version( + get_self_hosted_mysql_version(self.version()), + self, + event_details, + self.logger(), + ) } fn cloud_provider_name(&self) -> &str { @@ -152,17 +161,13 @@ impl Service for MySQL { } fn tera_context(&self, target: &DeploymentTarget) -> Result { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration)); let kubernetes = target.kubernetes; let environment = target.environment; let mut context = default_tera_context(self, kubernetes, environment); // we need the kubernetes config file to store tfstates file in kube secrets - let kube_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(path) => path, - Err(e) => { - return Err(e.to_legacy_engine_error()); - } - }; + let kube_config_file_path = kubernetes.get_kubeconfig_file_path()?; context.insert("kubeconfig_path", &kube_config_file_path); kubectl::kubectl_exec_create_namespace_without_labels( @@ -173,7 +178,7 @@ impl Service for MySQL { context.insert("namespace", environment.namespace()); - let version = &self.matching_correct_version()?; + let version = &self.matching_correct_version(event_details.clone())?.matched_version(); context.insert("version", &version); for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() { @@ -214,16 +219,12 @@ impl Service for MySQL { Ok(context) } - fn selector(&self) -> Option { - Some(format!("app={}", self.sanitized_name())) + fn logger(&self) -> &dyn Logger { + &*self.logger } - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::Database( - self.id().to_string(), - self.service_type().name().to_string(), - self.name().to_string(), - ) + fn selector(&self) -> Option { + Some(format!("app={}", self.sanitized_name())) } } @@ -270,12 +271,14 @@ impl Create for MySQL { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task( self, crate::cloud_provider::service::Action::Create, - Box::new(|| deploy_stateful_service(target, self, event_details.clone())), + Box::new(|| deploy_stateful_service(target, self, event_details.clone(), self.logger())), ) } @@ -286,11 +289,14 @@ impl Create for MySQL { #[named] fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -300,11 +306,14 @@ impl Create for MySQL { impl Pause for MySQL { #[named] fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || { @@ -318,11 +327,14 @@ impl Pause for MySQL { #[named] fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -338,12 +350,14 @@ impl Delete for MySQL { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task( self, crate::cloud_provider::service::Action::Delete, - Box::new(|| delete_stateful_service(target, self, event_details.clone())), + Box::new(|| delete_stateful_service(target, self, event_details.clone(), self.logger())), ) } @@ -353,11 +367,14 @@ impl Delete for MySQL { #[named] fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } diff --git a/src/cloud_provider/digitalocean/databases/postgresql.rs b/src/cloud_provider/digitalocean/databases/postgresql.rs index ff1b194e..b140205e 100644 --- a/src/cloud_provider/digitalocean/databases/postgresql.rs +++ b/src/cloud_provider/digitalocean/databases/postgresql.rs @@ -3,14 +3,15 @@ use tera::Context as TeraContext; use crate::cloud_provider::service::{ check_service_version, default_tera_context, delete_stateful_service, deploy_stateful_service, get_tfstate_name, get_tfstate_suffix, scale_down_database, send_progress_on_long_task, Action, Create, Database, DatabaseOptions, - DatabaseType, Delete, Helm, Pause, Service, ServiceType, StatefulService, Terraform, + DatabaseType, Delete, Helm, Pause, Service, ServiceType, ServiceVersionCheckResult, StatefulService, Terraform, }; use crate::cloud_provider::utilities::{get_self_hosted_postgres_version, print_action, sanitize_name}; use crate::cloud_provider::DeploymentTarget; use crate::cmd::helm::Timeout; use crate::cmd::kubectl; -use crate::error::{EngineError, EngineErrorScope}; -use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter}; +use crate::errors::EngineError; +use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter}; +use crate::logger::Logger; use crate::models::DatabaseMode::MANAGED; use crate::models::{Context, Listen, Listener, Listeners}; use ::function_name::named; @@ -28,6 +29,7 @@ pub struct PostgreSQL { database_instance_type: String, options: DatabaseOptions, listeners: Listeners, + logger: Box, } impl PostgreSQL { @@ -44,6 +46,7 @@ impl PostgreSQL { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: Box, ) -> Self { PostgreSQL { context, @@ -58,11 +61,17 @@ impl PostgreSQL { database_instance_type: database_instance_type.to_string(), options, listeners, + logger, } } - fn matching_correct_version(&self) -> Result { - check_service_version(get_self_hosted_postgres_version(self.version()), self) + fn matching_correct_version(&self, event_details: EventDetails) -> Result { + check_service_version( + get_self_hosted_postgres_version(self.version()), + self, + event_details, + self.logger(), + ) } fn cloud_provider_name(&self) -> &str { @@ -152,17 +161,13 @@ impl Service for PostgreSQL { } fn tera_context(&self, target: &DeploymentTarget) -> Result { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration)); let kubernetes = target.kubernetes; let environment = target.environment; let mut context = default_tera_context(self, kubernetes, environment); // we need the kubernetes config file to store tfstates file in kube secrets - let kube_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(path) => path, - Err(e) => { - return Err(e.to_legacy_engine_error()); - } - }; + let kube_config_file_path = kubernetes.get_kubeconfig_file_path()?; context.insert("kubeconfig_path", &kube_config_file_path); kubectl::kubectl_exec_create_namespace_without_labels( @@ -173,7 +178,7 @@ impl Service for PostgreSQL { context.insert("namespace", environment.namespace()); - let version = self.matching_correct_version()?; + let version = self.matching_correct_version(event_details.clone())?.matched_version(); context.insert("version", &version); for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() { @@ -220,12 +225,8 @@ impl Service for PostgreSQL { Some(format!("app={}", self.sanitized_name())) } - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::Database( - self.id().to_string(), - self.service_type().name().to_string(), - self.name().to_string(), - ) + fn logger(&self) -> &dyn Logger { + &*self.logger } } @@ -272,12 +273,14 @@ impl Create for PostgreSQL { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task( self, crate::cloud_provider::service::Action::Create, - Box::new(|| deploy_stateful_service(target, self, event_details.clone())), + Box::new(|| deploy_stateful_service(target, self, event_details.clone(), self.logger())), ) } @@ -287,11 +290,14 @@ impl Create for PostgreSQL { #[named] fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -301,11 +307,14 @@ impl Create for PostgreSQL { impl Pause for PostgreSQL { #[named] fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || { @@ -319,11 +328,14 @@ impl Pause for PostgreSQL { #[named] fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -339,12 +351,14 @@ impl Delete for PostgreSQL { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task( self, crate::cloud_provider::service::Action::Delete, - Box::new(|| delete_stateful_service(target, self, event_details.clone())), + Box::new(|| delete_stateful_service(target, self, event_details.clone(), self.logger())), ) } @@ -354,11 +368,14 @@ impl Delete for PostgreSQL { #[named] fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) diff --git a/src/cloud_provider/digitalocean/databases/redis.rs b/src/cloud_provider/digitalocean/databases/redis.rs index 148e449d..6985ca7e 100644 --- a/src/cloud_provider/digitalocean/databases/redis.rs +++ b/src/cloud_provider/digitalocean/databases/redis.rs @@ -3,14 +3,15 @@ use tera::Context as TeraContext; use crate::cloud_provider::service::{ check_service_version, default_tera_context, delete_stateful_service, deploy_stateful_service, get_tfstate_name, get_tfstate_suffix, scale_down_database, send_progress_on_long_task, Action, Create, Database, DatabaseOptions, - DatabaseType, Delete, Helm, Pause, Service, ServiceType, StatefulService, Terraform, + DatabaseType, Delete, Helm, Pause, Service, ServiceType, ServiceVersionCheckResult, StatefulService, Terraform, }; use crate::cloud_provider::utilities::{get_self_hosted_redis_version, print_action, sanitize_name}; use crate::cloud_provider::DeploymentTarget; use crate::cmd::helm::Timeout; use crate::cmd::kubectl; -use crate::error::{EngineError, EngineErrorScope}; -use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter}; +use crate::errors::EngineError; +use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter}; +use crate::logger::Logger; use crate::models::DatabaseMode::MANAGED; use crate::models::{Context, Listen, Listener, Listeners}; use ::function_name::named; @@ -28,6 +29,7 @@ pub struct Redis { database_instance_type: String, options: DatabaseOptions, listeners: Listeners, + logger: Box, } impl Redis { @@ -44,6 +46,7 @@ impl Redis { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: Box, ) -> Self { Self { context, @@ -58,11 +61,17 @@ impl Redis { database_instance_type: database_instance_type.to_string(), options, listeners, + logger, } } - fn matching_correct_version(&self) -> Result { - check_service_version(get_self_hosted_redis_version(self.version()), self) + fn matching_correct_version(&self, event_details: EventDetails) -> Result { + check_service_version( + get_self_hosted_redis_version(self.version()), + self, + event_details, + self.logger(), + ) } fn cloud_provider_name(&self) -> &str { @@ -152,17 +161,13 @@ impl Service for Redis { } fn tera_context(&self, target: &DeploymentTarget) -> Result { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration)); let kubernetes = target.kubernetes; let environment = target.environment; let mut context = default_tera_context(self, kubernetes, environment); // we need the kubernetes config file to store tfstates file in kube secrets - let kube_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(path) => path, - Err(e) => { - return Err(e.to_legacy_engine_error()); - } - }; + let kube_config_file_path = kubernetes.get_kubeconfig_file_path()?; context.insert("kubeconfig_path", &kube_config_file_path); kubectl::kubectl_exec_create_namespace_without_labels( @@ -171,7 +176,7 @@ impl Service for Redis { kubernetes.cloud_provider().credentials_environment_variables(), ); - let version = self.matching_correct_version()?; + let version = self.matching_correct_version(event_details.clone())?.matched_version(); context.insert("namespace", environment.namespace()); context.insert("version", &version); @@ -217,12 +222,8 @@ impl Service for Redis { Some(format!("app={}", self.sanitized_name())) } - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::Database( - self.id().to_string(), - self.service_type().name().to_string(), - self.name().to_string(), - ) + fn logger(&self) -> &dyn Logger { + &*self.logger } } @@ -269,12 +270,14 @@ impl Create for Redis { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task( self, crate::cloud_provider::service::Action::Create, - Box::new(|| deploy_stateful_service(target, self, event_details.clone())), + Box::new(|| deploy_stateful_service(target, self, event_details.clone(), self.logger())), ) } @@ -285,11 +288,14 @@ impl Create for Redis { #[named] fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } @@ -298,11 +304,14 @@ impl Create for Redis { impl Pause for Redis { #[named] fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || { @@ -316,11 +325,14 @@ impl Pause for Redis { #[named] fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } @@ -335,12 +347,14 @@ impl Delete for Redis { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task( self, crate::cloud_provider::service::Action::Pause, - Box::new(|| delete_stateful_service(target, self, event_details.clone())), + Box::new(|| delete_stateful_service(target, self, event_details.clone(), self.logger())), ) } @@ -350,11 +364,14 @@ impl Delete for Redis { #[named] fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } diff --git a/src/cloud_provider/digitalocean/kubernetes/doks_api.rs b/src/cloud_provider/digitalocean/kubernetes/doks_api.rs index 56aad133..e07688f6 100644 --- a/src/cloud_provider/digitalocean/kubernetes/doks_api.rs +++ b/src/cloud_provider/digitalocean/kubernetes/doks_api.rs @@ -63,12 +63,10 @@ fn get_do_kubernetes_latest_slug_version( doks_versions: &Vec, wished_version: &str, ) -> Result, CommandError> { - let wished_k8s_version = - VersionsNumber::from_str(wished_version).map_err(|e| CommandError::new_from_safe_message(e.to_string()))?; + let wished_k8s_version = VersionsNumber::from_str(wished_version)?; for kubernetes_doks_version in doks_versions { - let current_k8s_version = VersionsNumber::from_str(kubernetes_doks_version.kubernetes_version.as_str()) - .map_err(|e| CommandError::new_from_safe_message(e.to_string()))?; + let current_k8s_version = VersionsNumber::from_str(kubernetes_doks_version.kubernetes_version.as_str())?; if current_k8s_version.major == wished_k8s_version.major && current_k8s_version.minor == wished_k8s_version.minor { diff --git a/src/cloud_provider/digitalocean/kubernetes/mod.rs b/src/cloud_provider/digitalocean/kubernetes/mod.rs index 9a3e7466..fb2f2f49 100644 --- a/src/cloud_provider/digitalocean/kubernetes/mod.rs +++ b/src/cloud_provider/digitalocean/kubernetes/mod.rs @@ -133,7 +133,7 @@ impl<'a> DOKS<'a> { e, ); - logger.log(LogLevel::Error, EngineEvent::Error(err.clone())); + logger.log(LogLevel::Error, EngineEvent::Error(err.clone(), None)); return Err(err); } @@ -368,10 +368,13 @@ impl<'a> DOKS<'a> { Some(secret_id) => context.insert("vault_secret_id", secret_id.to_str().unwrap()), None => self.logger().log( LogLevel::Error, - EngineEvent::Error(EngineError::new_missing_required_env_variable( - event_details.clone(), - "VAULT_SECRET_ID".to_string(), - )), + EngineEvent::Error( + EngineError::new_missing_required_env_variable( + event_details.clone(), + "VAULT_SECRET_ID".to_string(), + ), + None, + ), ), } } @@ -399,7 +402,9 @@ impl<'a> DOKS<'a> { None => Err(EngineError::new_unsupported_version_error( event_details.clone(), self.kind().to_string(), - VersionsNumber::from_str(&self.version).expect("cannot parse version"), + VersionsNumber::from_str(&self.version) + .expect("cannot parse version") + .to_string(), )), Some(v) => Ok(v), }, @@ -503,7 +508,7 @@ impl<'a> DOKS<'a> { ) } Err(e) => { - self.logger().log(LogLevel::Error, EngineEvent::Error(e)); + self.logger().log(LogLevel::Error, EngineEvent::Error(e, None)); self.logger().log( LogLevel::Info, EngineEvent::Deploying( @@ -598,10 +603,10 @@ impl<'a> DOKS<'a> { } Err(e) => self.logger().log( LogLevel::Warning, - EngineEvent::Error(EngineError::new_terraform_state_does_not_exist( - event_details.clone(), - e, - )), + EngineEvent::Error( + EngineError::new_terraform_state_does_not_exist(event_details.clone(), e), + None, + ), ), }; @@ -619,7 +624,8 @@ impl<'a> DOKS<'a> { self.kubeconfig_bucket_name(), CommandError::new(e.message.unwrap_or("No error message".to_string()), None), ); - self.logger().log(LogLevel::Error, EngineEvent::Error(error.clone())); + self.logger() + .log(LogLevel::Error, EngineEvent::Error(error.clone(), None)); return Err(error); } @@ -630,7 +636,8 @@ impl<'a> DOKS<'a> { self.logs_bucket_name(), CommandError::new(e.message.unwrap_or("No error message".to_string()), None), ); - self.logger().log(LogLevel::Error, EngineEvent::Error(error.clone())); + self.logger() + .log(LogLevel::Error, EngineEvent::Error(error.clone(), None)); return Err(error); } @@ -661,7 +668,8 @@ impl<'a> DOKS<'a> { kubeconfig_name.to_string(), CommandError::new(e.message.unwrap_or("No error message".to_string()), None), ); - self.logger().log(LogLevel::Error, EngineEvent::Error(error.clone())); + self.logger() + .log(LogLevel::Error, EngineEvent::Error(error.clone(), None)); return Err(error); } @@ -782,11 +790,7 @@ impl<'a> DOKS<'a> { return Err(EngineError::new_k8s_loadbalancer_configuration_issue( event_details.clone(), CommandError::new( - format!( - "{}, error: {}.", - safe_message.to_string(), - e.message.unwrap_or("No error message".to_string()) - ), + format!("{}, error: {}.", safe_message.to_string(), e.message(),), Some(safe_message.to_string()), ), )); @@ -995,10 +999,10 @@ impl<'a> DOKS<'a> { // An issue occurred during the apply before destroy of Terraform, it may be expected if you're resuming a destroy self.logger().log( LogLevel::Error, - EngineEvent::Error(EngineError::new_terraform_error_while_executing_pipeline( - event_details.clone(), - e, - )), + EngineEvent::Error( + EngineError::new_terraform_error_while_executing_pipeline(event_details.clone(), e), + None, + ), ); }; @@ -1355,22 +1359,28 @@ impl<'a> Kubernetes for DOKS<'a> { #[named] fn on_create(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Create)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Create, || self.create()) } #[named] fn on_create_error(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Create)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Create, || self.create_error()) } @@ -1407,7 +1417,7 @@ impl<'a> Kubernetes for DOKS<'a> { self.cloud_provider().credentials_environment_variables(), event_details.stage().clone(), ) { - self.logger().log(LogLevel::Error, EngineEvent::Error(e.clone())); + self.logger().log(LogLevel::Error, EngineEvent::Error(e.clone(), None)); return Err(e); } @@ -1436,7 +1446,9 @@ impl<'a> Kubernetes for DOKS<'a> { return Err(EngineError::new_unsupported_version_error( event_details.clone(), self.kind().to_string(), - VersionsNumber::from_str(&self.version).expect("cannot parse version"), + VersionsNumber::from_str(&self.version) + .expect("cannot parse version") + .to_string(), )) } Some(v) => v, @@ -1528,88 +1540,112 @@ impl<'a> Kubernetes for DOKS<'a> { #[named] fn on_upgrade(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Upgrade)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Create, || self.upgrade()) } #[named] fn on_upgrade_error(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Upgrade)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Create, || self.upgrade_error()) } #[named] fn on_downgrade(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Downgrade)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Create, || self.downgrade()) } #[named] fn on_downgrade_error(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Downgrade)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Create, || self.downgrade_error()) } #[named] fn on_pause(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Pause, || self.pause()) } #[named] fn on_pause_error(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Pause, || self.pause_error()) } #[named] fn on_delete(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Delete, || self.delete()) } #[named] fn on_delete_error(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Delete, || self.delete_error()) } @@ -1622,8 +1658,10 @@ impl<'a> Kubernetes for DOKS<'a> { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); - kubernetes::deploy_environment(self, environment, event_details) + kubernetes::deploy_environment(self, environment, event_details, self.logger()) } #[named] @@ -1634,8 +1672,10 @@ impl<'a> Kubernetes for DOKS<'a> { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); - kubernetes::deploy_environment_error(self, environment, event_details) + kubernetes::deploy_environment_error(self, environment, event_details, self.logger()) } #[named] @@ -1646,17 +1686,22 @@ impl<'a> Kubernetes for DOKS<'a> { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); - kubernetes::pause_environment(self, environment, event_details) + kubernetes::pause_environment(self, environment, event_details, self.logger()) } #[named] fn pause_environment_error(&self, _environment: &Environment) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); Ok(()) } @@ -1669,17 +1714,22 @@ impl<'a> Kubernetes for DOKS<'a> { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); - kubernetes::delete_environment(self, environment, event_details) + kubernetes::delete_environment(self, environment, event_details, self.logger()) } #[named] fn delete_environment_error(&self, _environment: &Environment) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); Ok(()) } diff --git a/src/cloud_provider/digitalocean/mod.rs b/src/cloud_provider/digitalocean/mod.rs index 8c094fbf..36d2689d 100644 --- a/src/cloud_provider/digitalocean/mod.rs +++ b/src/cloud_provider/digitalocean/mod.rs @@ -7,8 +7,9 @@ use uuid::Uuid; use crate::cloud_provider::{CloudProvider, Kind, TerraformStateCredentials}; use crate::constants::DIGITAL_OCEAN_TOKEN; -use crate::error::{EngineError, EngineErrorCause}; -use crate::models::{Context, Listen, Listener, Listeners}; +use crate::errors::EngineError; +use crate::events::{EventDetails, GeneralStep, Stage, ToTransmitter, Transmitter}; +use crate::models::{Context, Listen, Listener, Listeners, QoveryIdentifier}; pub mod application; pub mod databases; @@ -100,16 +101,13 @@ impl CloudProvider for DO { } fn is_valid(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::General(GeneralStep::RetrieveClusterConfig)); let client = DigitalOcean::new(&self.token); match client { Ok(_x) => Ok(()), Err(_) => { - return Err(self.engine_error( - EngineErrorCause::User( - "Your DigitalOcean 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 Digital Ocean {}", self.name_with_id()), + return Err(EngineError::new_client_invalid_cloud_provider_credentials( + event_details, )); } } @@ -134,6 +132,19 @@ impl CloudProvider for DO { fn as_any(&self) -> &dyn Any { self } + + fn get_event_details(&self, stage: Stage) -> EventDetails { + let context = self.context(); + EventDetails::new( + None, + QoveryIdentifier::from(context.organization_id().to_string()), + QoveryIdentifier::from(context.cluster_id().to_string()), + QoveryIdentifier::from(context.execution_id().to_string()), + None, + stage, + self.to_transmitter(), + ) + } } impl Listen for DO { @@ -145,3 +156,9 @@ impl Listen for DO { self.listeners.push(listener); } } + +impl ToTransmitter for DO { + fn to_transmitter(&self) -> Transmitter { + Transmitter::CloudProvider(self.id.to_string(), self.name.to_string()) + } +} diff --git a/src/cloud_provider/digitalocean/network/load_balancer.rs b/src/cloud_provider/digitalocean/network/load_balancer.rs index 0457ecb2..ee9e2cd1 100644 --- a/src/cloud_provider/digitalocean/network/load_balancer.rs +++ b/src/cloud_provider/digitalocean/network/load_balancer.rs @@ -3,35 +3,34 @@ extern crate serde_json; use reqwest::StatusCode; use crate::cloud_provider::digitalocean::models::load_balancers::LoadBalancer; -use crate::error::{SimpleError, SimpleErrorKind}; +use crate::errors::CommandError; use crate::utilities::get_header_with_bearer; use std::net::Ipv4Addr; use std::str::FromStr; pub const DO_LOAD_BALANCER_API_PATH: &str = "https://api.digitalocean.com/v2/load_balancers"; -pub fn get_ip_from_do_load_balancer_api_output(json_content: &str) -> Result { +pub fn get_ip_from_do_load_balancer_api_output(json_content: &str) -> Result { let res_load_balancer = serde_json::from_str::(json_content); match res_load_balancer { Ok(lb) => match Ipv4Addr::from_str(&lb.load_balancer.ip) { Ok(ip) => Ok(ip), - Err(e) => Err(SimpleError::new( - SimpleErrorKind::Other, + Err(e) => Err(CommandError::new( + e.to_string(), Some(format!( - "Info returned from DO API is not a valid IP, received '{:?}' instead. {:?}", - &lb.load_balancer.ip, e + "Info returned from DO API is not a valid IP, received '{:?}' instead.", + &lb.load_balancer.ip, )), )), }, - Err(_) => Err(SimpleError::new( - SimpleErrorKind::Other, - Some("Error While trying to deserialize json received from Digital Ocean Load Balancer API".to_string()), + Err(_) => Err(CommandError::new_from_safe_message( + "Error While trying to deserialize json received from Digital Ocean Load Balancer API".to_string(), )), } } -pub fn do_get_load_balancer_ip(token: &str, load_balancer_id: &str) -> Result { +pub fn do_get_load_balancer_ip(token: &str, load_balancer_id: &str) -> Result { let headers = get_header_with_bearer(token); let url = format!("{}/{}", DO_LOAD_BALANCER_API_PATH, load_balancer_id); let res = reqwest::blocking::Client::new().get(&url).headers(headers).send(); @@ -42,17 +41,16 @@ pub fn do_get_load_balancer_ip(token: &str, load_balancer_id: &str) -> Result Err(SimpleError::new( - SimpleErrorKind::Other, + _ => Err(CommandError::new( + format!("{:?}", response), Some( - format!("Unknown status code received from Digital Ocean Kubernetes API while retrieving load balancer information. {:?}", response), + "Unknown status code received from Digital Ocean Kubernetes API while retrieving load balancer information.".to_string(), ), )), }, Err(_) => { - Err(SimpleError::new( - SimpleErrorKind::Other, - Some("Unable to get a response from Digital Ocean Load Balancer API"), + Err(CommandError::new_from_safe_message( + "Unable to get a response from Digital Ocean Load Balancer API".to_string(), )) } }; diff --git a/src/cloud_provider/digitalocean/router.rs b/src/cloud_provider/digitalocean/router.rs index ab48f336..7eb80eb6 100644 --- a/src/cloud_provider/digitalocean/router.rs +++ b/src/cloud_provider/digitalocean/router.rs @@ -4,15 +4,15 @@ use crate::cloud_provider::helm::ChartInfo; use crate::cloud_provider::models::{CustomDomain, CustomDomainDataTemplate, Route, RouteDataTemplate}; use crate::cloud_provider::service::{ default_tera_context, delete_router, deploy_stateless_service_error, send_progress_on_long_task, Action, Create, - Delete, Helm, Pause, Service, ServiceType, StatelessService, + Delete, Helm, Pause, Router as RRouter, Service, ServiceType, StatelessService, }; use crate::cloud_provider::utilities::{check_cname_for, print_action, sanitize_name}; use crate::cloud_provider::DeploymentTarget; use crate::cmd::helm; use crate::cmd::helm::Timeout; -use crate::error::{EngineError, EngineErrorCause, EngineErrorScope}; -use crate::errors::EngineError as NewEngineError; -use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter}; +use crate::errors::EngineError; +use crate::events::{EngineEvent, EnvironmentStep, EventMessage, Stage, ToTransmitter, Transmitter}; +use crate::logger::{LogLevel, Logger}; use crate::models::{Context, Listen, Listener, Listeners}; use ::function_name::named; @@ -26,6 +26,7 @@ pub struct Router { sticky_sessions_enabled: bool, routes: Vec, listeners: Listeners, + logger: Box, } impl Router { @@ -39,6 +40,7 @@ impl Router { routes: Vec, sticky_sessions_enabled: bool, listeners: Listeners, + logger: Box, ) -> Self { Router { context, @@ -50,6 +52,7 @@ impl Router { sticky_sessions_enabled, routes, listeners, + logger, } } @@ -124,6 +127,7 @@ impl Service for Router { } fn tera_context(&self, target: &DeploymentTarget) -> Result { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration)); let kubernetes = target.kubernetes; let environment = target.environment; let mut context = default_tera_context(self, kubernetes, environment); @@ -187,34 +191,44 @@ impl Service for Router { context.insert("nginx_requests_memory", "128Mi"); context.insert("nginx_limit_cpu", "200m"); context.insert("nginx_limit_memory", "128Mi"); - let kubernetes_config_file_path = kubernetes.get_kubeconfig_file_path(); - match kubernetes_config_file_path { - Ok(kubernetes_config_file_path_string) => { - // Default domain - let external_ingress_hostname_default = crate::cmd::kubectl::kubectl_exec_get_external_ingress_hostname( - kubernetes_config_file_path_string.as_str(), - "nginx-ingress", - "nginx-ingress-ingress-nginx-controller", - kubernetes.cloud_provider().credentials_environment_variables(), - ); + let kubernetes_config_file_path = kubernetes.get_kubeconfig_file_path()?; - match external_ingress_hostname_default { - Ok(external_ingress_hostname_default) => match external_ingress_hostname_default { - Some(hostname) => context.insert("external_ingress_hostname_default", hostname.as_str()), - None => { - return Err(self.engine_error( - EngineErrorCause::Internal, - "Error while trying to get Load Balancer hostname from Kubernetes cluster".into(), - )); - } - }, - _ => { - error!("can't fetch external ingress hostname"); - } + // Default domain + let external_ingress_hostname_default = crate::cmd::kubectl::kubectl_exec_get_external_ingress_hostname( + kubernetes_config_file_path, + "nginx-ingress", + "nginx-ingress-ingress-nginx-controller", + kubernetes.cloud_provider().credentials_environment_variables(), + ); + + match external_ingress_hostname_default { + Ok(external_ingress_hostname_default) => match external_ingress_hostname_default { + Some(hostname) => context.insert("external_ingress_hostname_default", hostname.as_str()), + None => { + // TODO(benjaminch): Handle better this one via a proper error eventually + self.logger().log( + LogLevel::Warning, + EngineEvent::Warning( + event_details.clone(), + EventMessage::new_from_safe( + "Error while trying to get Load Balancer hostname from Kubernetes cluster".to_string(), + ), + ), + ); } + }, + _ => { + // FIXME really? + // TODO(benjaminch): Handle better this one via a proper error eventually + self.logger().log( + LogLevel::Warning, + EngineEvent::Warning( + event_details.clone(), + EventMessage::new_from_safe("Can't fetch external ingress hostname.".to_string()), + ), + ); } - Err(_) => error!("can't fetch kubernetes config file - what's wrong? This must never happened"), } let router_default_domain_hash = crate::crypto::to_sha1_truncate_16(self.default_domain.as_str()); @@ -244,8 +258,8 @@ impl Service for Router { Some(format!("routerId={}", self.id)) } - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::Router(self.id().to_string(), self.name().to_string()) + fn logger(&self) -> &dyn Logger { + &*self.logger } } @@ -311,23 +325,22 @@ impl ToTransmitter for Router { impl Create for Router { #[named] fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); - let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); let kubernetes = target.kubernetes; let environment = target.environment; let workspace_dir = self.workspace_directory(); let helm_release_name = self.helm_release_name(); - let kubernetes_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(p) => p, - Err(e) => return Err(e.to_legacy_engine_error()), - }; + let kubernetes_config_file_path = kubernetes.get_kubeconfig_file_path()?; // respect order - getting the context here and not before is mandatory // the nginx-ingress must be available to get the external dns target if necessary @@ -337,13 +350,12 @@ impl Create for Router { if let Err(e) = crate::template::generate_and_copy_all_files_into_dir(from_dir.as_str(), workspace_dir.as_str(), context) { - return Err(NewEngineError::new_cannot_copy_files_from_one_directory_to_another( + return Err(EngineError::new_cannot_copy_files_from_one_directory_to_another( event_details.clone(), from_dir.to_string(), workspace_dir.to_string(), e, - ) - .to_legacy_engine_error()); + )); } // do exec helm upgrade and return the last deployment status @@ -351,7 +363,7 @@ impl Create for Router { &kubernetes_config_file_path, &kubernetes.cloud_provider().credentials_environment_variables(), ) - .map_err(|e| helm::to_engine_error(&event_details, e).to_legacy_engine_error())?; + .map_err(|e| helm::to_engine_error(&event_details, e))?; let chart = ChartInfo::new_from_custom_namespace( helm_release_name, workspace_dir.clone(), @@ -366,14 +378,14 @@ impl Create for Router { ); helm.upgrade(&chart, &vec![]) - .map_err(|e| helm::to_engine_error(&event_details, e).to_legacy_engine_error()) + .map_err(|e| helm::to_engine_error(&event_details, e)) } fn on_create_check(&self) -> Result<(), EngineError> { - use crate::cloud_provider::service::Router; + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); // check non custom domains - self.check_domains()?; + self.check_domains(event_details.clone(), self.logger())?; // Wait/Check that custom domain is a CNAME targeting qovery for domain_to_check in self.custom_domains.iter() { @@ -387,9 +399,19 @@ impl Create for Router { continue; } Ok(err) | Err(err) => { - warn!( - "Invalid CNAME for {}. Might not be an issue if user is using a CDN: {}", - domain_to_check.domain, err + // TODO(benjaminch): Handle better this one via a proper error eventually + self.logger().log( + LogLevel::Warning, + EngineEvent::Warning( + event_details.clone(), + EventMessage::new( + format!( + "Invalid CNAME for {}. Might not be an issue if user is using a CDN.", + domain_to_check.domain, + ), + Some(err.to_string()), + ), + ), ); } } @@ -400,11 +422,14 @@ impl Create for Router { #[named] fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { @@ -416,11 +441,14 @@ impl Create for Router { impl Pause for Router { #[named] fn on_pause(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } @@ -431,11 +459,14 @@ impl Pause for Router { #[named] fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } @@ -450,6 +481,8 @@ impl Delete for Router { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); delete_router(target, self, false, event_details) } @@ -466,6 +499,8 @@ impl Delete for Router { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); delete_router(target, self, true, event_details) } diff --git a/src/cloud_provider/environment.rs b/src/cloud_provider/environment.rs index 6fcf2f53..cc6da827 100644 --- a/src/cloud_provider/environment.rs +++ b/src/cloud_provider/environment.rs @@ -1,5 +1,5 @@ use crate::cloud_provider::service::{Action, StatefulService, StatelessService}; -use crate::error::EngineError; +use crate::errors::EngineError; use crate::unit_conversion::cpu_string_to_float; pub struct Environment { diff --git a/src/cloud_provider/kubernetes.rs b/src/cloud_provider/kubernetes.rs index a234fd96..7cdadc7f 100644 --- a/src/cloud_provider/kubernetes.rs +++ b/src/cloud_provider/kubernetes.rs @@ -16,7 +16,7 @@ use serde::{Deserialize, Serialize}; use crate::cloud_provider::aws::regions::AwsZones; use crate::cloud_provider::environment::Environment; -use crate::cloud_provider::models::NodeGroups; +use crate::cloud_provider::models::{CpuLimits, NodeGroups}; use crate::cloud_provider::service::CheckAction; use crate::cloud_provider::utilities::VersionsNumber; use crate::cloud_provider::{service, CloudProvider, DeploymentTarget}; @@ -96,8 +96,20 @@ pub trait Kubernetes: Listen { if Path::new(&local_kubeconfig_generated).exists() { match File::open(&local_kubeconfig_generated) { Ok(_) => Some(local_kubeconfig_generated), - Err(_) => { - debug!("couldn't open {} file", &local_kubeconfig_generated); + Err(err) => { + self.logger().log( + LogLevel::Debug, + EngineEvent::Debug( + self.get_event_details(stage.clone()), + EventMessage::new( + err.to_string(), + Some( + format!("Error, couldn't open {} file", &local_kubeconfig_generated,) + .to_string(), + ), + ), + ), + ); None } } @@ -124,7 +136,7 @@ pub trait Kubernetes: Listen { Ok((path, file)) => (path, file), Err(err) => { let error = EngineError::new_cannot_retrieve_cluster_config_file( - self.get_event_details(stage), + self.get_event_details(stage.clone()), CommandError::new_from_safe_message( format!( "Error getting file from store, error: {}", @@ -133,7 +145,8 @@ pub trait Kubernetes: Listen { .to_string(), ), ); - self.logger().log(LogLevel::Error, EngineEvent::Error(error.clone())); + self.logger() + .log(LogLevel::Error, EngineEvent::Error(error.clone(), None)); return Err(error); } } @@ -144,12 +157,13 @@ pub trait Kubernetes: Listen { Ok(metadata) => metadata, Err(err) => { let error = EngineError::new_cannot_retrieve_cluster_config_file( - self.get_event_details(stage), + self.get_event_details(stage.clone()), CommandError::new_from_safe_message( format!("Error getting file metadata, error: {}", err.to_string(),).to_string(), ), ); - self.logger().log(LogLevel::Error, EngineEvent::Error(error.clone())); + self.logger() + .log(LogLevel::Error, EngineEvent::Error(error.clone(), None)); return Err(error); } }; @@ -158,13 +172,14 @@ pub trait Kubernetes: Listen { permissions.set_mode(0o400); if let Err(err) = std::fs::set_permissions(string_path.as_str(), permissions) { let error = EngineError::new_cannot_retrieve_cluster_config_file( - self.get_event_details(stage), + self.get_event_details(stage.clone()), CommandError::new_from_safe_message(format!( "Error setting file permissions, error: {}", err.to_string(), )), ); - self.logger().log(LogLevel::Error, EngineEvent::Error(error.clone())); + self.logger() + .log(LogLevel::Error, EngineEvent::Error(error.clone(), None)); return Err(error); } @@ -193,7 +208,8 @@ pub trait Kubernetes: Listen { ), ); - self.logger().log(LogLevel::Error, EngineEvent::Error(error.clone())); + self.logger() + .log(LogLevel::Error, EngineEvent::Error(error.clone(), None)); return Err(error); } @@ -273,6 +289,7 @@ pub trait Kubernetes: Listen { Ok((path, _)) => path, Err(e) => return Err(CommandError::new(e.message(), None)), }; + send_progress_on_long_task(self, Action::Create, || { check_workers_status(&kubeconfig, self.cloud_provider().credentials_environment_variables()) }) @@ -403,6 +420,7 @@ pub fn deploy_environment( kubernetes: &dyn Kubernetes, environment: &Environment, event_details: EventDetails, + logger: &dyn Logger, ) -> Result<(), EngineError> { let listeners_helper = ListenersHelper::new(kubernetes.listeners()); @@ -421,11 +439,6 @@ pub fn deploy_environment( }, }; - // Resources check before deploy - // TODO(ENG-1066): we should check if env can be deployed or not based on required resources and available ones. - // This check is not trivial since auto-scaler comes to play and resources distribution on nodes might not behave necesarly the way we think. - // do not deploy if there is not enough resources - // create all stateful services (database) for service in &environment.stateful_services { let _ = service::check_kubernetes_service_error( @@ -433,6 +446,7 @@ pub fn deploy_environment( kubernetes, service, event_details.clone(), + logger, &stateful_deployment_target, &listeners_helper, "deployment", @@ -445,6 +459,7 @@ pub fn deploy_environment( kubernetes, service, event_details.clone(), + logger, &stateful_deployment_target, &listeners_helper, "check deployment", @@ -468,6 +483,7 @@ pub fn deploy_environment( kubernetes, service, event_details.clone(), + logger, &stateless_deployment_target, &listeners_helper, "deployment", @@ -485,6 +501,7 @@ pub fn deploy_environment( kubernetes, service, event_details.clone(), + logger.clone(), &stateless_deployment_target, &listeners_helper, "check deployment", @@ -501,6 +518,7 @@ pub fn deploy_environment( kubernetes, service, event_details.clone(), + logger, &stateless_deployment_target, &listeners_helper, "check deployment", @@ -516,6 +534,7 @@ pub fn deploy_environment_error( kubernetes: &dyn Kubernetes, environment: &Environment, event_details: EventDetails, + logger: &dyn Logger, ) -> Result<(), EngineError> { let listeners_helper = ListenersHelper::new(kubernetes.listeners()); @@ -540,6 +559,7 @@ pub fn deploy_environment_error( kubernetes, service, event_details.clone(), + logger, &stateful_deployment_target, &listeners_helper, "revert deployment", @@ -563,6 +583,7 @@ pub fn deploy_environment_error( kubernetes, service, event_details.clone(), + logger, &stateless_deployment_target, &listeners_helper, "revert deployment", @@ -578,6 +599,7 @@ pub fn pause_environment( kubernetes: &dyn Kubernetes, environment: &Environment, event_details: EventDetails, + logger: &dyn Logger, ) -> Result<(), EngineError> { let listeners_helper = ListenersHelper::new(kubernetes.listeners()); @@ -599,6 +621,7 @@ pub fn pause_environment( kubernetes, service, event_details.clone(), + logger, &stateless_deployment_target, &listeners_helper, "pause", @@ -616,6 +639,7 @@ pub fn pause_environment( kubernetes, service, event_details.clone(), + logger, &stateful_deployment_target, &listeners_helper, "pause", @@ -632,6 +656,7 @@ pub fn pause_environment( kubernetes, service, event_details.clone(), + logger, &stateless_deployment_target, &listeners_helper, "check pause", @@ -649,6 +674,7 @@ pub fn pause_environment( kubernetes, service, event_details.clone(), + logger, &stateful_deployment_target, &listeners_helper, "check pause", @@ -664,6 +690,7 @@ pub fn delete_environment( kubernetes: &dyn Kubernetes, environment: &Environment, event_details: EventDetails, + logger: &dyn Logger, ) -> Result<(), EngineError> { let listeners_helper = ListenersHelper::new(kubernetes.listeners()); @@ -685,6 +712,7 @@ pub fn delete_environment( kubernetes, service, event_details.clone(), + logger, &stateless_deployment_target, &listeners_helper, "delete", @@ -702,6 +730,7 @@ pub fn delete_environment( kubernetes, service, event_details.clone(), + logger, &stateful_deployment_target, &listeners_helper, "delete", @@ -718,6 +747,7 @@ pub fn delete_environment( kubernetes, service, event_details.clone(), + logger, &stateless_deployment_target, &listeners_helper, "delete check", @@ -735,6 +765,7 @@ pub fn delete_environment( kubernetes, service, event_details.clone(), + logger, &stateful_deployment_target, &listeners_helper, "delete check", @@ -780,8 +811,12 @@ where EngineEvent::Deleting( event_details.clone(), EventMessage::new( - format!("Encountering issues while trying to get objects kind {}.", object,), - Some(e.message()), + format!( + "Encountering issues while trying to get objects kind {}: {:?}", + object, + e.message() + ), + None, ), ), ); @@ -798,10 +833,7 @@ where LogLevel::Warning, EngineEvent::Deleting( event_details.clone(), - EventMessage::new_from_safe(format!( - "Failed to delete all {} objects, retrying...", - object, - )), + EventMessage::new(format!("Failed to delete all {} objects, retrying...", object,), None), ), ); OperationResult::Retry(e) @@ -1043,7 +1075,7 @@ fn check_kubernetes_upgrade_status( return Err(EngineError::new_cannot_determine_k8s_requested_upgrade_version( event_details.clone(), requested_version.to_string(), - Some(CommandError::new_from_safe_message(e)), + Some(e), )); } }; @@ -1256,35 +1288,222 @@ impl NodeGroups { } } +/// TODO(benjaminch): to be refactored with similar function in services.rs +/// This function call (start|pause|delete)_in_progress function every 10 seconds when a +/// long blocking task is running. +pub fn send_progress_on_long_task(kubernetes: &K, action: Action, long_task: F) -> R +where + K: Kubernetes + Listen, + F: Fn() -> R, +{ + let waiting_message = match action { + Action::Create => Some(format!( + "Infrastructure '{}' deployment is in progress...", + kubernetes.name_with_id() + )), + Action::Pause => Some(format!( + "Infrastructure '{}' pause is in progress...", + kubernetes.name_with_id() + )), + Action::Delete => Some(format!( + "Infrastructure '{}' deletion is in progress...", + kubernetes.name_with_id() + )), + Action::Nothing => None, + }; + + send_progress_on_long_task_with_message(kubernetes, waiting_message, action, long_task) +} + +/// TODO(benjaminch): to be refactored with similar function in services.rs +/// This function call (start|pause|delete)_in_progress function every 10 seconds when a +/// long blocking task is running. +pub fn send_progress_on_long_task_with_message( + kubernetes: &K, + waiting_message: Option, + action: Action, + long_task: F, +) -> R +where + K: Kubernetes + Listen, + F: Fn() -> R, +{ + let listeners = std::clone::Clone::clone(kubernetes.listeners()); + let logger = kubernetes.logger().clone_dyn(); + let event_details = kubernetes + .get_event_details(Stage::Infrastructure(InfrastructureStep::Create)) + .clone(); + + let progress_info = ProgressInfo::new( + ProgressScope::Infrastructure { + execution_id: kubernetes.context().execution_id().to_string(), + }, + Info, + waiting_message.clone(), + kubernetes.context().execution_id(), + ); + + let (tx, rx) = mpsc::channel(); + + // monitor thread to notify user while the blocking task is executed + let _ = std::thread::Builder::new() + .name("task-monitor".to_string()) + .spawn(move || { + // stop the thread when the blocking task is done + let listeners_helper = ListenersHelper::new(&listeners); + let action = action; + let progress_info = progress_info; + let waiting_message = waiting_message.unwrap_or("no message ...".to_string()); + + loop { + // do notify users here + let progress_info = std::clone::Clone::clone(&progress_info); + let event_details = std::clone::Clone::clone(&event_details); + let event_message = EventMessage::new_from_safe(waiting_message.to_string()); + + match action { + Action::Create => { + listeners_helper.deployment_in_progress(progress_info); + logger.log( + LogLevel::Info, + EngineEvent::Deploying( + EventDetails::clone_changing_stage( + event_details, + Stage::Infrastructure(InfrastructureStep::Create), + ), + event_message, + ), + ); + } + Action::Pause => { + listeners_helper.pause_in_progress(progress_info); + logger.log( + LogLevel::Info, + EngineEvent::Pausing( + EventDetails::clone_changing_stage( + event_details, + Stage::Infrastructure(InfrastructureStep::Pause), + ), + event_message, + ), + ); + } + Action::Delete => { + listeners_helper.delete_in_progress(progress_info); + logger.log( + LogLevel::Info, + EngineEvent::Deleting( + EventDetails::clone_changing_stage( + event_details, + Stage::Infrastructure(InfrastructureStep::Delete), + ), + event_message, + ), + ); + } + Action::Nothing => {} // should not happens + }; + + thread::sleep(Duration::from_secs(10)); + + // watch for thread termination + match rx.try_recv() { + Ok(_) | Err(TryRecvError::Disconnected) => break, + Err(TryRecvError::Empty) => {} + } + } + }); + + let blocking_task_result = long_task(); + let _ = tx.send(()); + + blocking_task_result +} + +pub fn validate_k8s_required_cpu_and_burstable( + listener_helper: &ListenersHelper, + execution_id: &str, + context_id: &str, + total_cpu: String, + cpu_burst: String, + event_details: EventDetails, + logger: &dyn Logger, +) -> Result { + let total_cpu_float = convert_k8s_cpu_value_to_f32(total_cpu.clone())?; + let cpu_burst_float = convert_k8s_cpu_value_to_f32(cpu_burst.clone())?; + let mut set_cpu_burst = cpu_burst.clone(); + + if cpu_burst_float < total_cpu_float { + let message = format!( + "CPU burst value '{}' was lower than the desired total of CPUs {}, using burstable value.", + cpu_burst, total_cpu, + ); + + listener_helper.error(ProgressInfo::new( + ProgressScope::Environment { + id: execution_id.to_string(), + }, + ProgressLevel::Warn, + Some(message.to_string()), + context_id, + )); + + logger.log( + LogLevel::Warning, + EngineEvent::Warning(event_details, EventMessage::new_from_safe(message)), + ); + + set_cpu_burst = total_cpu.clone(); + } + + Ok(CpuLimits { + cpu_limit: set_cpu_burst, + cpu_request: total_cpu, + }) +} + +pub fn convert_k8s_cpu_value_to_f32(value: String) -> Result { + if value.ends_with('m') { + let mut value_number_string = value; + value_number_string.pop(); + return match value_number_string.parse::() { + Ok(n) => { + Ok(n * 0.001) // return in milli cpu the value + } + Err(e) => Err(CommandError::new( + e.to_string(), + Some(format!( + "Error while trying to parse `{}` to float 32.", + value_number_string.as_str() + )), + )), + }; + } + + match value.parse::() { + Ok(n) => Ok(n), + Err(e) => Err(CommandError::new( + e.to_string(), + Some(format!("Error while trying to parse `{}` to float 32.", value.as_str())), + )), + } +} + #[cfg(test)] mod tests { + use crate::cloud_provider::Kind::Aws; use std::str::FromStr; use crate::cloud_provider::kubernetes::{ - check_kubernetes_upgrade_status, compare_kubernetes_cluster_versions_for_upgrade, KubernetesNodesType, + check_kubernetes_upgrade_status, compare_kubernetes_cluster_versions_for_upgrade, convert_k8s_cpu_value_to_f32, + validate_k8s_required_cpu_and_burstable, KubernetesNodesType, }; + use crate::cloud_provider::models::CpuLimits; use crate::cloud_provider::utilities::VersionsNumber; use crate::cmd::structs::{KubernetesList, KubernetesNode, KubernetesVersion}; - use crate::events::{EngineEvent, EventDetails, InfrastructureStep, Stage, Transmitter}; - use crate::logger::{LogLevel, Logger}; - use crate::models::QoveryIdentifier; - - #[derive(Clone)] - struct FakeLogger {} - - impl FakeLogger { - pub fn new() -> Self { - FakeLogger {} - } - } - - impl Logger for FakeLogger { - fn log(&self, _log_level: LogLevel, _event: EngineEvent) {} - - fn clone_dyn(&self) -> Box { - Box::new(self.clone()) - } - } + use crate::events::{EventDetails, InfrastructureStep, Stage, Transmitter}; + use crate::logger::StdIoLogger; + use crate::models::{ListenersHelper, QoveryIdentifier}; #[test] pub fn check_kubernetes_upgrade_method() { @@ -1299,7 +1518,7 @@ mod tests { Stage::Infrastructure(InfrastructureStep::Upgrade), Transmitter::Kubernetes(QoveryIdentifier::new_random().to_string(), "test".to_string()), ); - let logger = FakeLogger::new(); + let logger = StdIoLogger::new(); // test full cluster upgrade (masters + workers) let result = check_kubernetes_upgrade_status( @@ -1851,136 +2070,72 @@ mod tests { } } } -} -/// TODO(benjaminch): to be refactored with similar function in services.rs -/// This function call (start|pause|delete)_in_progress function every 10 seconds when a -/// long blocking task is running. -pub fn send_progress_on_long_task(kubernetes: &K, action: Action, long_task: F) -> R -where - K: Kubernetes + Listen, - F: Fn() -> R, -{ - let waiting_message = match action { - Action::Create => Some(format!( - "Infrastructure '{}' deployment is in progress...", - kubernetes.name_with_id() - )), - Action::Pause => Some(format!( - "Infrastructure '{}' pause is in progress...", - kubernetes.name_with_id() - )), - Action::Delete => Some(format!( - "Infrastructure '{}' deletion is in progress...", - kubernetes.name_with_id() - )), - Action::Nothing => None, - }; + #[test] + pub fn test_k8s_milli_cpu_convert() { + let milli_cpu = "250m".to_string(); + let int_cpu = "2".to_string(); - send_progress_on_long_task_with_message(kubernetes, waiting_message, action, long_task) -} + assert_eq!(convert_k8s_cpu_value_to_f32(milli_cpu).unwrap(), 0.25 as f32); + assert_eq!(convert_k8s_cpu_value_to_f32(int_cpu).unwrap(), 2 as f32); + } -/// TODO(benjaminch): to be refactored with similar function in services.rs -/// This function call (start|pause|delete)_in_progress function every 10 seconds when a -/// long blocking task is running. -pub fn send_progress_on_long_task_with_message( - kubernetes: &K, - waiting_message: Option, - action: Action, - long_task: F, -) -> R -where - K: Kubernetes + Listen, - F: Fn() -> R, -{ - let listeners = std::clone::Clone::clone(kubernetes.listeners()); - let logger = kubernetes.logger().clone_dyn(); - let event_details = kubernetes - .get_event_details(Stage::Infrastructure(InfrastructureStep::Create)) - .clone(); // TODO(benjaminch): change the way event details is built, it's very dirty to make it mut and change the stage :( + #[test] + pub fn test_cpu_set() { + let v = vec![]; + let listener_helper = ListenersHelper::new(&v); + let logger = StdIoLogger::new(); + let execution_id = "execution_id"; + let context_id = "context_id"; + let organization_id = "organization_id"; + let cluster_id = "cluster_id"; - let progress_info = ProgressInfo::new( - ProgressScope::Infrastructure { - execution_id: kubernetes.context().execution_id().to_string(), - }, - Info, - waiting_message.clone(), - kubernetes.context().execution_id(), - ); + let event_details = EventDetails::new( + Some(Aws), + QoveryIdentifier::new(organization_id.to_string()), + QoveryIdentifier::new(cluster_id.to_string()), + QoveryIdentifier::new(execution_id.to_string()), + Some("region_fake".to_string()), + Stage::Infrastructure(InfrastructureStep::LoadConfiguration), + Transmitter::Kubernetes(cluster_id.to_string(), format!("{}-name", cluster_id)), + ); - let (tx, rx) = mpsc::channel(); - - // monitor thread to notify user while the blocking task is executed - let _ = std::thread::Builder::new() - .name("task-monitor".to_string()) - .spawn(move || { - // stop the thread when the blocking task is done - let listeners_helper = ListenersHelper::new(&listeners); - let action = action; - let progress_info = progress_info; - let waiting_message = waiting_message.unwrap_or("no message ...".to_string()); - - loop { - // do notify users here - let progress_info = std::clone::Clone::clone(&progress_info); - let event_details = std::clone::Clone::clone(&event_details); - let event_message = EventMessage::new_from_safe(waiting_message.to_string()); - - match action { - Action::Create => { - listeners_helper.deployment_in_progress(progress_info); - logger.log( - LogLevel::Info, - EngineEvent::Deploying( - EventDetails::clone_changing_stage( - event_details, - Stage::Infrastructure(InfrastructureStep::Create), - ), - event_message, - ), - ); - } - Action::Pause => { - listeners_helper.pause_in_progress(progress_info); - logger.log( - LogLevel::Info, - EngineEvent::Pausing( - EventDetails::clone_changing_stage( - event_details, - Stage::Infrastructure(InfrastructureStep::Pause), - ), - event_message, - ), - ); - } - Action::Delete => { - listeners_helper.delete_in_progress(progress_info); - logger.log( - LogLevel::Info, - EngineEvent::Deleting( - EventDetails::clone_changing_stage( - event_details, - Stage::Infrastructure(InfrastructureStep::Delete), - ), - event_message, - ), - ); - } - Action::Nothing => {} // should not happens - }; - - thread::sleep(Duration::from_secs(10)); - - // watch for thread termination - match rx.try_recv() { - Ok(_) | Err(TryRecvError::Disconnected) => break, - Err(TryRecvError::Empty) => {} - } + let mut total_cpu = "0.25".to_string(); + let mut cpu_burst = "1".to_string(); + assert_eq!( + validate_k8s_required_cpu_and_burstable( + &listener_helper, + execution_id, + context_id, + total_cpu, + cpu_burst, + event_details.clone(), + &logger + ) + .unwrap(), + CpuLimits { + cpu_request: "0.25".to_string(), + cpu_limit: "1".to_string() } - }); + ); - let blocking_task_result = long_task(); - let _ = tx.send(()); - - blocking_task_result + total_cpu = "1".to_string(); + cpu_burst = "0.5".to_string(); + assert_eq!( + validate_k8s_required_cpu_and_burstable( + &listener_helper, + execution_id, + context_id, + total_cpu, + cpu_burst, + event_details.clone(), + &logger + ) + .unwrap(), + CpuLimits { + cpu_request: "1".to_string(), + cpu_limit: "1".to_string() + } + ); + } } diff --git a/src/cloud_provider/mod.rs b/src/cloud_provider/mod.rs index 0919f37c..eb9a412a 100644 --- a/src/cloud_provider/mod.rs +++ b/src/cloud_provider/mod.rs @@ -5,7 +5,8 @@ use serde::{Deserialize, Serialize}; use crate::cloud_provider::environment::Environment; use crate::cloud_provider::kubernetes::Kubernetes; -use crate::error::{EngineError, EngineErrorCause, EngineErrorScope}; +use crate::errors::EngineError; +use crate::events::{EventDetails, Stage, ToTransmitter}; use crate::models::{Context, Listen}; pub mod aws; @@ -21,7 +22,7 @@ pub mod scaleway; pub mod service; pub mod utilities; -pub trait CloudProvider: Listen { +pub trait CloudProvider: Listen + ToTransmitter { fn context(&self) -> &Context; fn kind(&self) -> Kind; fn id(&self) -> &str; @@ -41,18 +42,8 @@ pub trait CloudProvider: Listen { /// environment variables to inject to generate Terraform files from templates fn tera_context_environment_variables(&self) -> Vec<(&str, &str)>; 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; + fn get_event_details(&self, stage: Stage) -> EventDetails; } #[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)] diff --git a/src/cloud_provider/scaleway/application.rs b/src/cloud_provider/scaleway/application.rs index ab587a86..7405d0bf 100644 --- a/src/cloud_provider/scaleway/application.rs +++ b/src/cloud_provider/scaleway/application.rs @@ -4,6 +4,7 @@ use std::str::FromStr; use tera::Context as TeraContext; use crate::build_platform::Image; +use crate::cloud_provider::kubernetes::validate_k8s_required_cpu_and_burstable; use crate::cloud_provider::models::{ EnvironmentVariable, EnvironmentVariableDataTemplate, Storage, StorageDataTemplate, }; @@ -12,14 +13,13 @@ use crate::cloud_provider::service::{ scale_down_application, send_progress_on_long_task, Action, Application as CApplication, Create, Delete, Helm, Pause, Service, ServiceType, StatelessService, }; -use crate::cloud_provider::utilities::{print_action, sanitize_name, validate_k8s_required_cpu_and_burstable}; +use crate::cloud_provider::utilities::{print_action, sanitize_name}; use crate::cloud_provider::DeploymentTarget; use crate::cmd::helm::Timeout; use crate::cmd::kubectl::ScalingKind::{Deployment, Statefulset}; -use crate::error::EngineErrorCause::Internal; -use crate::error::{EngineError, EngineErrorScope}; -use crate::errors::CommandError; -use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter}; +use crate::errors::{CommandError, EngineError}; +use crate::events::{EngineEvent, EnvironmentStep, EventMessage, Stage, ToTransmitter, Transmitter}; +use crate::logger::{LogLevel, Logger}; use crate::models::{Context, Listen, Listener, Listeners, ListenersHelper, Port}; use ::function_name::named; @@ -39,6 +39,7 @@ pub struct Application { storage: Vec>, environment_variables: Vec, listeners: Listeners, + logger: Box, } impl Application { @@ -58,7 +59,8 @@ impl Application { storage: Vec>, environment_variables: Vec, listeners: Listeners, - ) -> Application { + logger: Box, + ) -> Self { Application { context, id: id.to_string(), @@ -75,6 +77,7 @@ impl Application { storage, environment_variables, listeners, + logger, } } @@ -196,6 +199,7 @@ impl Service for Application { } fn tera_context(&self, target: &DeploymentTarget) -> Result { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration)); let kubernetes = target.kubernetes; let environment = target.environment; let mut context = default_tera_context(self, kubernetes, environment); @@ -210,9 +214,15 @@ impl Service for Application { ), None => { let image_name_with_tag = self.image().name_with_tag(); - warn!( - "there is no registry url, use image name with tag with the default container registry: {}", - image_name_with_tag.as_str() + self.logger().log( + LogLevel::Warning, + EngineEvent::Warning( + event_details.clone(), + EventMessage::new_from_safe(format!( + "there is no registry url, use image name with tag with the default container registry: {}", + image_name_with_tag.as_str() + )), + ), ); context.insert("image_name_with_tag", image_name_with_tag.as_str()); } @@ -246,14 +256,16 @@ impl Service for Application { &self.id, self.total_cpus(), self.cpu_burst(), + event_details.clone(), + self.logger(), ) { Ok(l) => l, Err(e) => { - return Err(EngineError::new( - Internal, - EngineErrorScope::Application(self.id().to_string(), self.name().to_string()), - self.context.execution_id(), - Some(e.to_string()), + return Err(EngineError::new_k8s_validate_required_cpu_and_burstable_error( + event_details.clone(), + self.total_cpus(), + self.cpu_burst(), + e, )); } }; @@ -310,19 +322,22 @@ impl Service for Application { Some(format!("appId={}", self.id)) } - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::Application(self.id().to_string(), self.name().to_string()) + fn logger(&self) -> &dyn Logger { + &*self.logger } } impl Create for Application { #[named] fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { @@ -336,11 +351,14 @@ impl Create for Application { #[named] fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { @@ -352,11 +370,14 @@ impl Create for Application { impl Pause for Application { #[named] fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || { @@ -375,11 +396,14 @@ impl Pause for Application { #[named] fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -395,6 +419,8 @@ impl Delete for Application { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || { @@ -414,6 +440,8 @@ impl Delete for Application { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || { diff --git a/src/cloud_provider/scaleway/databases/mongodb.rs b/src/cloud_provider/scaleway/databases/mongodb.rs index e7ca00c1..c1cb6d76 100644 --- a/src/cloud_provider/scaleway/databases/mongodb.rs +++ b/src/cloud_provider/scaleway/databases/mongodb.rs @@ -3,14 +3,15 @@ use tera::Context as TeraContext; use crate::cloud_provider::service::{ check_service_version, default_tera_context, delete_stateful_service, deploy_stateful_service, get_tfstate_name, get_tfstate_suffix, scale_down_database, send_progress_on_long_task, Action, Create, Database, DatabaseOptions, - DatabaseType, Delete, Helm, Pause, Service, ServiceType, StatefulService, Terraform, + DatabaseType, Delete, Helm, Pause, Service, ServiceType, ServiceVersionCheckResult, StatefulService, Terraform, }; use crate::cloud_provider::utilities::{get_self_hosted_mongodb_version, print_action, sanitize_name}; use crate::cloud_provider::DeploymentTarget; use crate::cmd::helm::Timeout; use crate::cmd::kubectl; -use crate::error::{EngineError, EngineErrorScope}; -use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter}; +use crate::errors::EngineError; +use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter}; +use crate::logger::Logger; use crate::models::DatabaseMode::MANAGED; use crate::models::{Context, Listen, Listener, Listeners}; use ::function_name::named; @@ -28,6 +29,7 @@ pub struct MongoDB { database_instance_type: String, options: DatabaseOptions, listeners: Listeners, + logger: Box, } impl MongoDB { @@ -44,6 +46,7 @@ impl MongoDB { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: Box, ) -> Self { MongoDB { context, @@ -58,11 +61,17 @@ impl MongoDB { database_instance_type: database_instance_type.to_string(), options, listeners, + logger, } } - fn matching_correct_version(&self) -> Result { - check_service_version(get_self_hosted_mongodb_version(self.version()), self) + fn matching_correct_version(&self, event_details: EventDetails) -> Result { + check_service_version( + get_self_hosted_mongodb_version(self.version()), + self, + event_details, + self.logger(), + ) } fn cloud_provider_name(&self) -> &str { @@ -152,18 +161,15 @@ impl Service for MongoDB { } fn tera_context(&self, target: &DeploymentTarget) -> Result { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration)); let kubernetes = target.kubernetes; let environment = target.environment; let mut context = default_tera_context(self, kubernetes, environment); // we need the kubernetes config file to store tfstates file in kube secrets - let kube_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(path) => path, - Err(e) => { - return Err(e.to_legacy_engine_error()); - } - }; + let kube_config_file_path = kubernetes.get_kubeconfig_file_path()?; + context.insert("kubeconfig_path", &kube_config_file_path); kubectl::kubectl_exec_create_namespace_without_labels( @@ -174,7 +180,7 @@ impl Service for MongoDB { context.insert("namespace", environment.namespace()); - let version = self.matching_correct_version()?; + let version = self.matching_correct_version(event_details.clone())?.matched_version(); context.insert("version", &version); for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() { @@ -215,16 +221,12 @@ impl Service for MongoDB { Ok(context) } - fn selector(&self) -> Option { - Some(format!("app={}", self.sanitized_name())) + fn logger(&self) -> &dyn Logger { + &*self.logger } - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::Database( - self.id().to_string(), - self.service_type().name().to_string(), - self.name().to_string(), - ) + fn selector(&self) -> Option { + Some(format!("app={}", self.sanitized_name())) } } @@ -271,24 +273,35 @@ impl Create for MongoDB { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { - deploy_stateful_service(target, self, event_details.clone()) + deploy_stateful_service(target, self, event_details.clone(), self.logger()) }) } fn on_create_check(&self) -> Result<(), EngineError> { - self.check_domains(self.listeners.clone(), vec![self.fqdn.as_str()]) + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); + self.check_domains( + self.listeners.clone(), + vec![self.fqdn.as_str()], + event_details, + self.logger(), + ) } #[named] fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } @@ -297,11 +310,14 @@ impl Create for MongoDB { impl Pause for MongoDB { #[named] fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || { @@ -315,11 +331,14 @@ impl Pause for MongoDB { #[named] fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -335,10 +354,12 @@ impl Delete for MongoDB { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || { - delete_stateful_service(target, self, event_details.clone()) + delete_stateful_service(target, self, event_details.clone(), self.logger()) }) } @@ -348,11 +369,14 @@ impl Delete for MongoDB { #[named] fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } diff --git a/src/cloud_provider/scaleway/databases/mysql.rs b/src/cloud_provider/scaleway/databases/mysql.rs index b2fd053a..4f6472c3 100644 --- a/src/cloud_provider/scaleway/databases/mysql.rs +++ b/src/cloud_provider/scaleway/databases/mysql.rs @@ -3,7 +3,7 @@ use tera::Context as TeraContext; use crate::cloud_provider::service::{ check_service_version, default_tera_context, delete_stateful_service, deploy_stateful_service, get_tfstate_name, get_tfstate_suffix, scale_down_database, send_progress_on_long_task, Action, Create, Database, DatabaseOptions, - DatabaseType, Delete, Helm, Pause, Service, ServiceType, StatefulService, Terraform, + DatabaseType, Delete, Helm, Pause, Service, ServiceType, ServiceVersionCheckResult, StatefulService, Terraform, }; use crate::cloud_provider::utilities::{ get_self_hosted_mysql_version, get_supported_version_to_use, print_action, sanitize_name, VersionsNumber, @@ -11,13 +11,13 @@ use crate::cloud_provider::utilities::{ use crate::cloud_provider::DeploymentTarget; use crate::cmd::helm::Timeout; use crate::cmd::kubectl; -use crate::error::{EngineError, EngineErrorCause, EngineErrorScope, StringError}; -use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter}; +use crate::errors::{CommandError, EngineError}; +use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter}; +use crate::logger::Logger; use crate::models::DatabaseMode::MANAGED; use crate::models::{Context, Listen, Listener, Listeners}; use ::function_name::named; use std::collections::HashMap; -use std::str::FromStr; pub struct MySQL { context: Context, @@ -32,6 +32,7 @@ pub struct MySQL { database_instance_type: String, options: DatabaseOptions, listeners: Listeners, + logger: Box, } impl MySQL { @@ -48,6 +49,7 @@ impl MySQL { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: Box, ) -> Self { Self { context, @@ -62,21 +64,24 @@ impl MySQL { database_instance_type: database_instance_type.to_string(), options, listeners, + logger, } } - fn matching_correct_version(&self, is_managed_services: bool) -> Result { - let version = check_service_version(Self::pick_mysql_version(self.version(), is_managed_services), self)?; - match VersionsNumber::from_str(version.as_str()) { - Ok(res) => Ok(res), - Err(e) => Err(self.engine_error( - EngineErrorCause::Internal, - format!("cannot parse database version, err: {}", e), - )), - } + fn matching_correct_version( + &self, + is_managed_services: bool, + event_details: EventDetails, + ) -> Result { + check_service_version( + Self::pick_mysql_version(self.version(), is_managed_services), + self, + event_details, + self.logger(), + ) } - fn pick_mysql_version(requested_version: String, is_managed_service: bool) -> Result { + fn pick_mysql_version(requested_version: String, is_managed_service: bool) -> Result { if is_managed_service { Self::pick_managed_mysql_version(requested_version) } else { @@ -84,7 +89,7 @@ impl MySQL { } } - fn pick_managed_mysql_version(requested_version: String) -> Result { + fn pick_managed_mysql_version(requested_version: String) -> Result { // Scaleway supported MySQL versions // https://api.scaleway.com/rdb/v1/regions/fr-par/database-engines let mut supported_mysql_versions = HashMap::new(); @@ -183,18 +188,14 @@ impl Service for MySQL { } fn tera_context(&self, target: &DeploymentTarget) -> Result { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration)); let kubernetes = target.kubernetes; let environment = target.environment; let mut context = default_tera_context(self, kubernetes, environment); // we need the kubernetes config file to store tfstates file in kube secrets - let kube_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(path) => path, - Err(e) => { - return Err(e.to_legacy_engine_error()); - } - }; + let kube_config_file_path = kubernetes.get_kubeconfig_file_path()?; context.insert("kubeconfig_path", &kube_config_file_path); kubectl::kubectl_exec_create_namespace_without_labels( @@ -205,7 +206,9 @@ impl Service for MySQL { context.insert("namespace", environment.namespace()); - let version = &self.matching_correct_version(self.is_managed_service())?; + let version = &self + .matching_correct_version(self.is_managed_service(), event_details.clone())? + .matched_version(); context.insert("version_major", &version.to_major_version_string()); context.insert("version", &version.to_string()); // Scaleway needs to have major version only @@ -256,12 +259,8 @@ impl Service for MySQL { Some(format!("app={}", self.sanitized_name())) } - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::Database( - self.id().to_string(), - self.service_type().name().to_string(), - self.name().to_string(), - ) + fn logger(&self) -> &dyn Logger { + &*self.logger } } @@ -308,24 +307,35 @@ impl Create for MySQL { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { - deploy_stateful_service(target, self, event_details.clone()) + deploy_stateful_service(target, self, event_details.clone(), self.logger()) }) } fn on_create_check(&self) -> Result<(), EngineError> { - self.check_domains(self.listeners.clone(), vec![self.fqdn.as_str()]) + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); + self.check_domains( + self.listeners.clone(), + vec![self.fqdn.as_str()], + event_details, + self.logger(), + ) } #[named] fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -335,11 +345,14 @@ impl Create for MySQL { impl Pause for MySQL { #[named] fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || { @@ -353,11 +366,14 @@ impl Pause for MySQL { #[named] fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -373,10 +389,12 @@ impl Delete for MySQL { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || { - delete_stateful_service(target, self, event_details.clone()) + delete_stateful_service(target, self, event_details.clone(), self.logger()) }) } @@ -386,11 +404,14 @@ impl Delete for MySQL { #[named] fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } diff --git a/src/cloud_provider/scaleway/databases/postgresql.rs b/src/cloud_provider/scaleway/databases/postgresql.rs index 81626556..c5b30b5e 100644 --- a/src/cloud_provider/scaleway/databases/postgresql.rs +++ b/src/cloud_provider/scaleway/databases/postgresql.rs @@ -3,7 +3,7 @@ use tera::Context as TeraContext; use crate::cloud_provider::service::{ check_service_version, default_tera_context, delete_stateful_service, deploy_stateful_service, get_tfstate_name, get_tfstate_suffix, scale_down_database, send_progress_on_long_task, Action, Create, Database, DatabaseOptions, - DatabaseType, Delete, Helm, Pause, Service, ServiceType, StatefulService, Terraform, + DatabaseType, Delete, Helm, Pause, Service, ServiceType, ServiceVersionCheckResult, StatefulService, Terraform, }; use crate::cloud_provider::utilities::{ get_self_hosted_postgres_version, get_supported_version_to_use, print_action, sanitize_name, VersionsNumber, @@ -11,13 +11,13 @@ use crate::cloud_provider::utilities::{ use crate::cloud_provider::DeploymentTarget; use crate::cmd::helm::Timeout; use crate::cmd::kubectl; -use crate::error::{EngineError, EngineErrorCause, EngineErrorScope, StringError}; -use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter}; +use crate::errors::{CommandError, EngineError}; +use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter}; +use crate::logger::Logger; use crate::models::DatabaseMode::MANAGED; use crate::models::{Context, Listen, Listener, Listeners}; use ::function_name::named; use std::collections::HashMap; -use std::str::FromStr; pub struct PostgreSQL { context: Context, @@ -32,6 +32,7 @@ pub struct PostgreSQL { database_instance_type: String, options: DatabaseOptions, listeners: Listeners, + logger: Box, } impl PostgreSQL { @@ -48,6 +49,7 @@ impl PostgreSQL { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: Box, ) -> Self { Self { context, @@ -62,21 +64,24 @@ impl PostgreSQL { database_instance_type: database_instance_type.to_string(), options, listeners, + logger, } } - fn matching_correct_version(&self, is_managed_services: bool) -> Result { - let version = check_service_version(Self::pick_postgres_version(self.version(), is_managed_services), self)?; - match VersionsNumber::from_str(version.as_str()) { - Ok(res) => Ok(res), - Err(e) => Err(self.engine_error( - EngineErrorCause::Internal, - format!("cannot parse database version, err: {}", e), - )), - } + fn matching_correct_version( + &self, + is_managed_services: bool, + event_details: EventDetails, + ) -> Result { + check_service_version( + Self::pick_postgres_version(self.version(), is_managed_services), + self, + event_details, + self.logger(), + ) } - fn pick_postgres_version(requested_version: String, is_managed_service: bool) -> Result { + fn pick_postgres_version(requested_version: String, is_managed_service: bool) -> Result { if is_managed_service { Self::pick_managed_postgres_version(requested_version) } else { @@ -84,7 +89,7 @@ impl PostgreSQL { } } - fn pick_managed_postgres_version(requested_version: String) -> Result { + fn pick_managed_postgres_version(requested_version: String) -> Result { // Scaleway supported postgres versions // https://api.scaleway.com/rdb/v1/regions/fr-par/database-engines let mut supported_postgres_versions = HashMap::new(); @@ -192,18 +197,14 @@ impl Service for PostgreSQL { } fn tera_context(&self, target: &DeploymentTarget) -> Result { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration)); let kubernetes = target.kubernetes; let environment = target.environment; let mut context = default_tera_context(self, kubernetes, environment); // we need the kubernetes config file to store tfstates file in kube secrets - let kube_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(path) => path, - Err(e) => { - return Err(e.to_legacy_engine_error()); - } - }; + let kube_config_file_path = kubernetes.get_kubeconfig_file_path()?; context.insert("kubeconfig_path", &kube_config_file_path); kubectl::kubectl_exec_create_namespace_without_labels( @@ -214,7 +215,9 @@ impl Service for PostgreSQL { context.insert("namespace", environment.namespace()); - let version = &self.matching_correct_version(self.is_managed_service())?; + let version = &self + .matching_correct_version(self.is_managed_service(), event_details.clone())? + .matched_version(); context.insert("version_major", &version.to_major_version_string()); context.insert("version", &version.to_string()); // Scaleway needs to have major version only @@ -265,12 +268,8 @@ impl Service for PostgreSQL { Some(format!("app={}", self.sanitized_name())) } - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::Database( - self.id().to_string(), - self.service_type().name().to_string(), - self.name().to_string(), - ) + fn logger(&self) -> &dyn Logger { + &*self.logger } } @@ -317,24 +316,35 @@ impl Create for PostgreSQL { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { - deploy_stateful_service(target, self, event_details.clone()) + deploy_stateful_service(target, self, event_details.clone(), self.logger()) }) } fn on_create_check(&self) -> Result<(), EngineError> { - self.check_domains(self.listeners.clone(), vec![self.fqdn.as_str()]) + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); + self.check_domains( + self.listeners.clone(), + vec![self.fqdn.as_str()], + event_details, + self.logger(), + ) } #[named] fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -344,11 +354,14 @@ impl Create for PostgreSQL { impl Pause for PostgreSQL { #[named] fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || { @@ -362,11 +375,14 @@ impl Pause for PostgreSQL { #[named] fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) @@ -382,10 +398,12 @@ impl Delete for PostgreSQL { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || { - delete_stateful_service(target, self, event_details.clone()) + delete_stateful_service(target, self, event_details.clone(), self.logger()) }) } @@ -395,11 +413,14 @@ impl Delete for PostgreSQL { #[named] fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } diff --git a/src/cloud_provider/scaleway/databases/redis.rs b/src/cloud_provider/scaleway/databases/redis.rs index 5e371b24..b6086d43 100644 --- a/src/cloud_provider/scaleway/databases/redis.rs +++ b/src/cloud_provider/scaleway/databases/redis.rs @@ -3,14 +3,15 @@ use tera::Context as TeraContext; use crate::cloud_provider::service::{ check_service_version, default_tera_context, delete_stateful_service, deploy_stateful_service, get_tfstate_name, get_tfstate_suffix, scale_down_database, send_progress_on_long_task, Action, Create, Database, DatabaseOptions, - DatabaseType, Delete, Helm, Pause, Service, ServiceType, StatefulService, Terraform, + DatabaseType, Delete, Helm, Pause, Service, ServiceType, ServiceVersionCheckResult, StatefulService, Terraform, }; use crate::cloud_provider::utilities::{get_self_hosted_redis_version, print_action, sanitize_name}; use crate::cloud_provider::DeploymentTarget; use crate::cmd::helm::Timeout; use crate::cmd::kubectl; -use crate::error::{EngineError, EngineErrorScope}; -use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter}; +use crate::errors::EngineError; +use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter}; +use crate::logger::Logger; use crate::models::DatabaseMode::MANAGED; use crate::models::{Context, Listen, Listener, Listeners}; use ::function_name::named; @@ -28,6 +29,7 @@ pub struct Redis { database_instance_type: String, options: DatabaseOptions, listeners: Listeners, + logger: Box, } impl Redis { @@ -44,6 +46,7 @@ impl Redis { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: Box, ) -> Self { Self { context, @@ -58,11 +61,17 @@ impl Redis { database_instance_type: database_instance_type.to_string(), options, listeners, + logger, } } - fn matching_correct_version(&self) -> Result { - check_service_version(get_self_hosted_redis_version(self.version()), self) + fn matching_correct_version(&self, event_details: EventDetails) -> Result { + check_service_version( + get_self_hosted_redis_version(self.version()), + self, + event_details, + self.logger(), + ) } fn cloud_provider_name(&self) -> &str { @@ -152,18 +161,14 @@ impl Service for Redis { } fn tera_context(&self, target: &DeploymentTarget) -> Result { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration)); let kubernetes = target.kubernetes; let environment = target.environment; let mut context = default_tera_context(self, kubernetes, environment); // we need the kubernetes config file to store tfstates file in kube secrets - let kube_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(path) => path, - Err(e) => { - return Err(e.to_legacy_engine_error()); - } - }; + let kube_config_file_path = kubernetes.get_kubeconfig_file_path()?; context.insert("kubeconfig_path", &kube_config_file_path); kubectl::kubectl_exec_create_namespace_without_labels( @@ -172,7 +177,7 @@ impl Service for Redis { kubernetes.cloud_provider().credentials_environment_variables(), ); - let version = self.matching_correct_version()?; + let version = self.matching_correct_version(event_details.clone())?.matched_version(); context.insert("namespace", environment.namespace()); context.insert("version", &version); @@ -218,12 +223,8 @@ impl Service for Redis { Some(format!("app={}", self.sanitized_name())) } - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::Database( - self.id().to_string(), - self.service_type().name().to_string(), - self.name().to_string(), - ) + fn logger(&self) -> &dyn Logger { + &*self.logger } } @@ -270,24 +271,35 @@ impl Create for Redis { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { - deploy_stateful_service(target, self, event_details.clone()) + deploy_stateful_service(target, self, event_details.clone(), self.logger()) }) } fn on_create_check(&self) -> Result<(), EngineError> { - self.check_domains(self.listeners.clone(), vec![self.fqdn.as_str()]) + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); + self.check_domains( + self.listeners.clone(), + vec![self.fqdn.as_str()], + event_details, + self.logger(), + ) } #[named] fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } @@ -296,11 +308,14 @@ impl Create for Redis { impl Pause for Redis { #[named] fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || { @@ -314,11 +329,14 @@ impl Pause for Redis { #[named] fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } @@ -333,10 +351,12 @@ impl Delete for Redis { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || { - delete_stateful_service(target, self, event_details.clone()) + delete_stateful_service(target, self, event_details.clone(), self.logger()) }) } @@ -346,11 +366,14 @@ impl Delete for Redis { #[named] fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } diff --git a/src/cloud_provider/scaleway/kubernetes/mod.rs b/src/cloud_provider/scaleway/kubernetes/mod.rs index ea977e8c..fe935286 100644 --- a/src/cloud_provider/scaleway/kubernetes/mod.rs +++ b/src/cloud_provider/scaleway/kubernetes/mod.rs @@ -171,7 +171,7 @@ impl<'a> Kapsule<'a> { e, ); - logger.log(LogLevel::Error, EngineEvent::Error(err.clone())); + logger.log(LogLevel::Error, EngineEvent::Error(err.clone(), None)); return Err(err); } @@ -395,14 +395,17 @@ impl<'a> Kapsule<'a> { self.logger.log( LogLevel::Error, - EngineEvent::Error(EngineError::new_missing_workers_group_info_error( - event_details, - CommandError::new_from_safe_message(format!( - "Missing node pool info {} for cluster {}", - name, - self.context.cluster_id() - )), - )), + EngineEvent::Error( + EngineError::new_missing_workers_group_info_error( + event_details, + CommandError::new_from_safe_message(format!( + "Missing node pool info {} for cluster {}", + name, + self.context.cluster_id() + )), + ), + None, + ), ); if item.is_none() { @@ -550,10 +553,13 @@ impl<'a> Kapsule<'a> { Some(secret_id) => context.insert("vault_secret_id", secret_id.to_str().unwrap()), None => self.logger().log( LogLevel::Error, - EngineEvent::Error(EngineError::new_missing_required_env_variable( - event_details.clone(), - "VAULT_SECRET_ID".to_string(), - )), + EngineEvent::Error( + EngineError::new_missing_required_env_variable( + event_details.clone(), + "VAULT_SECRET_ID".to_string(), + ), + None, + ), ), } } @@ -635,7 +641,7 @@ impl<'a> Kapsule<'a> { ) } Err(e) => { - self.logger().log(LogLevel::Error, EngineEvent::Error(e)); + self.logger().log(LogLevel::Error, EngineEvent::Error(e, None)); self.logger().log( LogLevel::Info, EngineEvent::Deploying( @@ -726,10 +732,10 @@ impl<'a> Kapsule<'a> { } Err(e) => self.logger().log( LogLevel::Warning, - EngineEvent::Error(EngineError::new_terraform_state_does_not_exist( - event_details.clone(), - e, - )), + EngineEvent::Error( + EngineError::new_terraform_state_does_not_exist(event_details.clone(), e), + None, + ), ), }; @@ -751,7 +757,8 @@ impl<'a> Kapsule<'a> { self.kubeconfig_bucket_name(), CommandError::new(e.message.unwrap_or("No error message".to_string()), None), ); - self.logger().log(LogLevel::Error, EngineEvent::Error(error.clone())); + self.logger() + .log(LogLevel::Error, EngineEvent::Error(error.clone(), None)); return Err(error); } @@ -762,7 +769,8 @@ impl<'a> Kapsule<'a> { self.logs_bucket_name(), CommandError::new(e.message.unwrap_or("No error message".to_string()), None), ); - self.logger().log(LogLevel::Error, EngineEvent::Error(error.clone())); + self.logger() + .log(LogLevel::Error, EngineEvent::Error(error.clone(), None)); return Err(error); } @@ -793,7 +801,8 @@ impl<'a> Kapsule<'a> { kubeconfig_name.to_string(), CommandError::new(e.message.unwrap_or("No error message".to_string()), None), ); - self.logger().log(LogLevel::Error, EngineEvent::Error(error.clone())); + self.logger() + .log(LogLevel::Error, EngineEvent::Error(error.clone(), None)); return Err(error); } @@ -912,14 +921,14 @@ impl<'a> Kapsule<'a> { Some(c), ); self.logger - .log(LogLevel::Error, EngineEvent::Error(current_error.clone())); + .log(LogLevel::Error, EngineEvent::Error(current_error.clone(), None)); OperationResult::Retry(current_error) } ScwNodeGroupErrors::ClusterDoesNotExists(c) => { let current_error = EngineError::new_no_cluster_found_error(event_details.clone(), c); self.logger - .log(LogLevel::Error, EngineEvent::Error(current_error.clone())); + .log(LogLevel::Error, EngineEvent::Error(current_error.clone(), None)); OperationResult::Retry(current_error) } ScwNodeGroupErrors::MultipleClusterFound => { @@ -943,7 +952,7 @@ impl<'a> Kapsule<'a> { Some(c), ); self.logger - .log(LogLevel::Error, EngineEvent::Error(current_error.clone())); + .log(LogLevel::Error, EngineEvent::Error(current_error.clone(), None)); OperationResult::Retry(current_error) } } @@ -1191,7 +1200,8 @@ impl<'a> Kapsule<'a> { } Err(e) => { let error = EngineError::new_terraform_state_does_not_exist(event_details.clone(), e); - self.logger().log(LogLevel::Error, EngineEvent::Error(error.clone())); + self.logger() + .log(LogLevel::Error, EngineEvent::Error(error.clone(), None)); return Err(error); } }; @@ -1399,10 +1409,10 @@ impl<'a> Kapsule<'a> { // An issue occurred during the apply before destroy of Terraform, it may be expected if you're resuming a destroy self.logger().log( LogLevel::Error, - EngineEvent::Error(EngineError::new_terraform_error_while_executing_pipeline( - event_details.clone(), - e, - )), + EngineEvent::Error( + EngineError::new_terraform_error_while_executing_pipeline(event_details.clone(), e), + None, + ), ); }; @@ -1759,22 +1769,28 @@ impl<'a> Kubernetes for Kapsule<'a> { #[named] fn on_create(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Create)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Create, || self.create()) } #[named] fn on_create_error(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Create)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Create, || self.create_error()) } @@ -1811,7 +1827,7 @@ impl<'a> Kubernetes for Kapsule<'a> { self.cloud_provider().credentials_environment_variables(), Stage::Infrastructure(InfrastructureStep::Upgrade), ) { - self.logger().log(LogLevel::Error, EngineEvent::Error(e.clone())); + self.logger().log(LogLevel::Error, EngineEvent::Error(e.clone(), None)); return Err(e); } @@ -1911,88 +1927,112 @@ impl<'a> Kubernetes for Kapsule<'a> { #[named] fn on_upgrade(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Upgrade)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Create, || self.upgrade()) } #[named] fn on_upgrade_error(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Upgrade)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Create, || self.upgrade_error()) } #[named] fn on_downgrade(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Downgrade)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Create, || self.downgrade()) } #[named] fn on_downgrade_error(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Downgrade)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Create, || self.downgrade_error()) } #[named] fn on_pause(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Pause, || self.pause()) } #[named] fn on_pause_error(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Pause, || self.pause_error()) } #[named] fn on_delete(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Delete, || self.delete()) } #[named] fn on_delete_error(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Infrastructure(InfrastructureStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); send_progress_on_long_task(self, Action::Delete, || self.delete_error()) } @@ -2005,8 +2045,10 @@ impl<'a> Kubernetes for Kapsule<'a> { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); - kubernetes::deploy_environment(self, environment, event_details) + kubernetes::deploy_environment(self, environment, event_details, self.logger()) } #[named] @@ -2017,8 +2059,10 @@ impl<'a> Kubernetes for Kapsule<'a> { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); - kubernetes::deploy_environment_error(self, environment, event_details) + kubernetes::deploy_environment_error(self, environment, event_details, self.logger()) } #[named] @@ -2029,17 +2073,22 @@ impl<'a> Kubernetes for Kapsule<'a> { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); - kubernetes::pause_environment(self, environment, event_details) + kubernetes::pause_environment(self, environment, event_details, self.logger()) } #[named] fn pause_environment_error(&self, _environment: &Environment) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); Ok(()) } @@ -2052,17 +2101,22 @@ impl<'a> Kubernetes for Kapsule<'a> { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); - kubernetes::delete_environment(self, environment, event_details) + kubernetes::delete_environment(self, environment, event_details, self.logger()) } #[named] fn delete_environment_error(&self, _environment: &Environment) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); Ok(()) } diff --git a/src/cloud_provider/scaleway/mod.rs b/src/cloud_provider/scaleway/mod.rs index 1a2c7c66..fa03ff8c 100644 --- a/src/cloud_provider/scaleway/mod.rs +++ b/src/cloud_provider/scaleway/mod.rs @@ -3,7 +3,8 @@ use uuid::Uuid; use crate::cloud_provider::{CloudProvider, EngineError, Kind, TerraformStateCredentials}; use crate::constants::{SCALEWAY_ACCESS_KEY, SCALEWAY_DEFAULT_PROJECT_ID, SCALEWAY_SECRET_KEY}; -use crate::models::{Context, Listen, Listener, Listeners}; +use crate::events::{EventDetails, Stage, ToTransmitter, Transmitter}; +use crate::models::{Context, Listen, Listener, Listeners, QoveryIdentifier}; pub mod application; pub mod databases; @@ -119,6 +120,19 @@ impl CloudProvider for Scaleway { fn as_any(&self) -> &dyn Any { self } + + fn get_event_details(&self, stage: Stage) -> EventDetails { + let context = self.context(); + EventDetails::new( + None, + QoveryIdentifier::from(context.organization_id().to_string()), + QoveryIdentifier::from(context.cluster_id().to_string()), + QoveryIdentifier::from(context.execution_id().to_string()), + None, + stage, + self.to_transmitter(), + ) + } } impl Listen for Scaleway { @@ -130,3 +144,9 @@ impl Listen for Scaleway { self.listeners.push(listener); } } + +impl ToTransmitter for Scaleway { + fn to_transmitter(&self) -> Transmitter { + Transmitter::CloudProvider(self.id.to_string(), self.name.to_string()) + } +} diff --git a/src/cloud_provider/scaleway/router.rs b/src/cloud_provider/scaleway/router.rs index 53a60057..6db22aca 100644 --- a/src/cloud_provider/scaleway/router.rs +++ b/src/cloud_provider/scaleway/router.rs @@ -10,9 +10,9 @@ use crate::cloud_provider::utilities::{check_cname_for, print_action, sanitize_n use crate::cloud_provider::DeploymentTarget; use crate::cmd::helm; use crate::cmd::helm::Timeout; -use crate::error::{EngineError, EngineErrorScope}; -use crate::errors::EngineError as NewEngineError; -use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter}; +use crate::errors::EngineError; +use crate::events::{EngineEvent, EnvironmentStep, EventMessage, Stage, ToTransmitter, Transmitter}; +use crate::logger::{LogLevel, Logger}; use crate::models::{Context, Listen, Listener, Listeners}; use ::function_name::named; @@ -26,6 +26,7 @@ pub struct Router { sticky_sessions_enabled: bool, routes: Vec, listeners: Listeners, + logger: Box, } impl Router { @@ -39,7 +40,8 @@ impl Router { routes: Vec, sticky_sessions_enabled: bool, listeners: Listeners, - ) -> Router { + logger: Box, + ) -> Self { Router { context, id: id.to_string(), @@ -50,6 +52,7 @@ impl Router { sticky_sessions_enabled, routes, listeners, + logger, } } @@ -195,8 +198,8 @@ impl Service for Router { Some(format!("routerId={}", self.id)) } - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::Router(self.id().to_string(), self.name().to_string()) + fn logger(&self) -> &dyn Logger { + &*self.logger } } @@ -259,23 +262,22 @@ impl ToTransmitter for Router { impl Create for Router { #[named] fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); - let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); let kubernetes = target.kubernetes; let environment = target.environment; let workspace_dir = self.workspace_directory(); let helm_release_name = self.helm_release_name(); - let kubernetes_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(p) => p, - Err(e) => return Err(e.to_legacy_engine_error()), - }; + let kubernetes_config_file_path = kubernetes.get_kubeconfig_file_path()?; // respect order - getting the context here and not before is mandatory // the nginx-ingress must be available to get the external dns target if necessary @@ -285,13 +287,12 @@ impl Create for Router { if let Err(e) = crate::template::generate_and_copy_all_files_into_dir(from_dir.as_str(), workspace_dir.as_str(), context) { - return Err(NewEngineError::new_cannot_copy_files_from_one_directory_to_another( + return Err(EngineError::new_cannot_copy_files_from_one_directory_to_another( event_details.clone(), from_dir.to_string(), workspace_dir.to_string(), e, - ) - .to_legacy_engine_error()); + )); } // do exec helm upgrade and return the last deployment status @@ -299,7 +300,8 @@ impl Create for Router { &kubernetes_config_file_path, &kubernetes.cloud_provider().credentials_environment_variables(), ) - .map_err(|e| helm::to_engine_error(&event_details, e).to_legacy_engine_error())?; + .map_err(|e| helm::to_engine_error(&event_details, e))?; + let chart = ChartInfo::new_from_custom_namespace( helm_release_name, workspace_dir.clone(), @@ -314,12 +316,14 @@ impl Create for Router { ); helm.upgrade(&chart, &vec![]) - .map_err(|e| helm::to_engine_error(&event_details, e).to_legacy_engine_error()) + .map_err(|e| helm::to_engine_error(&event_details, e)) } fn on_create_check(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); + // check non custom domains - self.check_domains()?; + self.check_domains(event_details.clone(), self.logger())?; // Wait/Check that custom domain is a CNAME targeting qovery for domain_to_check in self.custom_domains.iter() { @@ -333,9 +337,19 @@ impl Create for Router { continue } Ok(err) | Err(err) => { - warn!( - "Invalid CNAME for {}. Might not be an issue if user is using a CDN: {}", - domain_to_check.domain, err + // TODO(benjaminch): Handle better this one via a proper error eventually + self.logger().log( + LogLevel::Warning, + EngineEvent::Warning( + event_details.clone(), + EventMessage::new( + format!( + "Invalid CNAME for {}. Might not be an issue if user is using a CDN.", + domain_to_check.domain, + ), + Some(err.to_string()), + ), + ), ); } } @@ -346,11 +360,14 @@ impl Create for Router { #[named] fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { @@ -362,11 +379,14 @@ impl Create for Router { impl Pause for Router { #[named] fn on_pause(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } @@ -377,11 +397,14 @@ impl Pause for Router { #[named] fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> { + let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); print_action( self.cloud_provider_name(), self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); Ok(()) } @@ -396,6 +419,8 @@ impl Delete for Router { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); delete_router(target, self, false, event_details) } @@ -412,6 +437,8 @@ impl Delete for Router { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); delete_router(target, self, true, event_details) } diff --git a/src/cloud_provider/service.rs b/src/cloud_provider/service.rs index fad1e3b7..c0cb6f98 100644 --- a/src/cloud_provider/service.rs +++ b/src/cloud_provider/service.rs @@ -1,4 +1,5 @@ use std::net::TcpStream; +use std::str::FromStr; use std::sync::mpsc; use std::sync::mpsc::TryRecvError; use std::thread; @@ -10,7 +11,7 @@ use crate::build_platform::Image; use crate::cloud_provider::environment::Environment; use crate::cloud_provider::helm::ChartInfo; use crate::cloud_provider::kubernetes::Kubernetes; -use crate::cloud_provider::utilities::check_domain_for; +use crate::cloud_provider::utilities::{check_domain_for, VersionsNumber}; use crate::cloud_provider::DeploymentTarget; use crate::cmd; use crate::cmd::helm; @@ -18,10 +19,9 @@ use crate::cmd::helm::Timeout; use crate::cmd::kubectl::ScalingKind::Statefulset; use crate::cmd::kubectl::{kubectl_exec_delete_secret, kubectl_exec_scale_replicas_by_selector, ScalingKind}; use crate::cmd::structs::LabelsContent; -use crate::error::StringError; -use crate::error::{EngineError, EngineErrorCause, EngineErrorScope}; -use crate::errors::{CommandError, EngineError as NewEngineError}; -use crate::events::{EnvironmentStep, EventDetails, GeneralStep, Stage, ToTransmitter}; +use crate::errors::{CommandError, EngineError}; +use crate::events::{EngineEvent, EnvironmentStep, EventDetails, EventMessage, GeneralStep, Stage, ToTransmitter}; +use crate::logger::{LogLevel, Logger}; use crate::models::ProgressLevel::Info; use crate::models::{ Context, DatabaseMode, Listen, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope, @@ -88,9 +88,15 @@ pub trait Service: ToTransmitter { } fn tera_context(&self, target: &DeploymentTarget) -> Result; // used to retrieve logs by using Kubernetes labels (selector) + fn logger(&self) -> &dyn Logger; fn selector(&self) -> Option; - fn debug_logs(&self, deployment_target: &DeploymentTarget, event_details: EventDetails) -> Vec { - debug_logs(self, deployment_target, event_details) + fn debug_logs( + &self, + deployment_target: &DeploymentTarget, + event_details: EventDetails, + logger: &dyn Logger, + ) -> Vec { + debug_logs(self, deployment_target, event_details, logger) } fn is_listening(&self, ip: &str) -> bool { let private_port = match self.private_port() { @@ -100,25 +106,15 @@ pub trait Service: ToTransmitter { TcpStream::connect(format!("{}:{}", ip, private_port)).is_ok() } - fn engine_error_scope(&self) -> EngineErrorScope; - fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError { - EngineError::new( - cause, - self.engine_error_scope(), - self.context().execution_id(), - Some(message), - ) - } fn is_valid(&self) -> Result<(), EngineError> { let binaries = ["kubectl", "helm", "terraform", "aws-iam-authenticator"]; for binary in binaries.iter() { if !crate::cmd::command::does_binary_exist(binary) { - return Err(NewEngineError::new_missing_required_binary( + return Err(EngineError::new_missing_required_binary( self.get_event_details(Stage::General(GeneralStep::ValidateSystemRequirements)), binary.to_string(), - ) - .to_legacy_engine_error()); + )); } } @@ -188,25 +184,35 @@ pub trait Application: StatelessService { pub trait Router: StatelessService + Listen + Helm { fn domains(&self) -> Vec<&str>; fn has_custom_domains(&self) -> bool; - fn check_domains(&self) -> Result<(), EngineError> { + fn check_domains(&self, event_details: EventDetails, logger: &dyn Logger) -> Result<(), EngineError> { check_domain_for( ListenersHelper::new(self.listeners()), self.domains(), self.id(), self.context().execution_id(), + event_details, + logger, )?; Ok(()) } } pub trait Database: StatefulService { - fn check_domains(&self, listeners: Listeners, domains: Vec<&str>) -> Result<(), EngineError> { + fn check_domains( + &self, + listeners: Listeners, + domains: Vec<&str>, + event_details: EventDetails, + logger: &dyn Logger, + ) -> Result<(), EngineError> { if self.publicly_accessible() { check_domain_for( ListenersHelper::new(&listeners), domains, self.id(), self.context().execution_id(), + event_details, + logger, )?; } Ok(()) @@ -309,7 +315,12 @@ impl<'a> ToString for ServiceType<'a> { } } -pub fn debug_logs(service: &T, deployment_target: &DeploymentTarget, event_details: EventDetails) -> Vec +pub fn debug_logs( + service: &T, + deployment_target: &DeploymentTarget, + event_details: EventDetails, + logger: &dyn Logger, +) -> Vec where T: Service + ?Sized, { @@ -318,12 +329,18 @@ where match get_stateless_resource_information_for_user(kubernetes, environment, service, event_details.clone()) { Ok(lines) => lines, Err(err) => { - error!( - "error while retrieving debug logs from {} {}; error: {:?}", - service.service_type().name(), - service.name_with_id(), - err + logger.log( + LogLevel::Error, + EngineEvent::Error( + err, + Some(EventMessage::new_from_safe(format!( + "error while retrieving debug logs from {} {}", + service.service_type().name(), + service.name_with_id(), + ))), + ), ); + Vec::new() } } @@ -387,20 +404,16 @@ where workspace_dir.as_str(), tera_context, ) { - return Err(NewEngineError::new_cannot_copy_files_from_one_directory_to_another( + return Err(EngineError::new_cannot_copy_files_from_one_directory_to_another( event_details.clone(), service.helm_chart_dir(), workspace_dir.to_string(), e, - ) - .to_legacy_engine_error()); + )); } let helm_release_name = service.helm_release_name(); - let kubernetes_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(path) => path, - Err(e) => return Err(e.to_legacy_engine_error()), - }; + let kubernetes_config_file_path = kubernetes.get_kubeconfig_file_path()?; // define labels to add to namespace let namespace_labels = service.context().resource_expiration_in_seconds().map(|_| { @@ -420,8 +433,7 @@ where kubernetes.cloud_provider().credentials_environment_variables(), ) .map_err(|e| { - NewEngineError::new_k8s_create_namespace(event_details.clone(), environment.namespace().to_string(), e) - .to_legacy_engine_error() + EngineError::new_k8s_create_namespace(event_details.clone(), environment.namespace().to_string(), e) })?; // do exec helm upgrade and return the last deployment status @@ -429,7 +441,7 @@ where &kubernetes_config_file_path, &kubernetes.cloud_provider().credentials_environment_variables(), ) - .map_err(|e| helm::to_engine_error(&event_details, e).to_legacy_engine_error())?; + .map_err(|e| helm::to_engine_error(&event_details, e))?; let chart = ChartInfo::new_from_custom_namespace( helm_release_name, workspace_dir.clone(), @@ -444,7 +456,7 @@ where ); helm.upgrade(&chart, &vec![]) - .map_err(|e| helm::to_engine_error(&event_details, e).to_legacy_engine_error())?; + .map_err(|e| helm::to_engine_error(&event_details, e))?; crate::cmd::kubectl::kubectl_exec_is_pod_ready_with_retry( kubernetes_config_file_path.as_str(), @@ -453,13 +465,12 @@ where kubernetes.cloud_provider().credentials_environment_variables(), ) .map_err(|e| { - NewEngineError::new_k8s_pod_not_ready( + EngineError::new_k8s_pod_not_ready( event_details.clone(), service.selector().unwrap_or("".to_string()), environment.namespace().to_string(), e, ) - .to_legacy_engine_error() })?; Ok(()) @@ -488,10 +499,7 @@ pub fn scale_down_database( let event_details = service.get_event_details(Stage::Environment(EnvironmentStep::ScaleDown)); let kubernetes = target.kubernetes; let environment = target.environment; - let kubernetes_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(path) => path, - Err(e) => return Err(e.to_legacy_engine_error()), - }; + let kubernetes_config_file_path = kubernetes.get_kubeconfig_file_path()?; let selector = format!("databaseId={}", service.id()); kubectl_exec_scale_replicas_by_selector( @@ -503,14 +511,13 @@ pub fn scale_down_database( replicas_count as u32, ) .map_err(|e| { - NewEngineError::new_k8s_scale_replicas( + EngineError::new_k8s_scale_replicas( event_details.clone(), selector.to_string(), environment.namespace().to_string(), replicas_count as u32, e, ) - .to_legacy_engine_error() }) } @@ -523,10 +530,7 @@ pub fn scale_down_application( let event_details = service.get_event_details(Stage::Environment(EnvironmentStep::ScaleDown)); let kubernetes = target.kubernetes; let environment = target.environment; - let kubernetes_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(path) => path, - Err(e) => return Err(e.to_legacy_engine_error()), - }; + let kubernetes_config_file_path = kubernetes.get_kubeconfig_file_path()?; kubectl_exec_scale_replicas_by_selector( kubernetes_config_file_path, @@ -537,14 +541,13 @@ pub fn scale_down_application( replicas_count as u32, ) .map_err(|e| { - NewEngineError::new_k8s_scale_replicas( + EngineError::new_k8s_scale_replicas( event_details.clone(), service.selector().unwrap_or("".to_string()), environment.namespace().to_string(), replicas_count as u32, e, ) - .to_legacy_engine_error() }) } @@ -599,6 +602,7 @@ pub fn deploy_stateful_service( target: &DeploymentTarget, service: &T, event_details: EventDetails, + logger: &dyn Logger, ) -> Result<(), EngineError> where T: StatefulService + Helm + Terraform, @@ -608,11 +612,16 @@ where let environment = target.environment; if service.is_managed_service() { - info!( - "deploy {} with name {} on {}", - service.service_type().name(), - service.name_with_id(), - kubernetes.cloud_provider().kind().to_string() + logger.log( + LogLevel::Info, + EngineEvent::Deploying( + event_details.clone(), + EventMessage::new_from_safe(format!( + "Deploying managed {} `{}`", + service.service_type().name(), + service.name_with_id() + )), + ), ); let context = service.tera_context(target)?; @@ -622,13 +631,12 @@ where &workspace_dir, context.clone(), ) { - return Err(NewEngineError::new_cannot_copy_files_from_one_directory_to_another( + return Err(EngineError::new_cannot_copy_files_from_one_directory_to_another( event_details.clone(), service.terraform_common_resource_dir_path(), workspace_dir.to_string(), e, - ) - .to_legacy_engine_error()); + )); } if let Err(e) = crate::template::generate_and_copy_all_files_into_dir( @@ -636,13 +644,12 @@ where &workspace_dir, context.clone(), ) { - return Err(NewEngineError::new_cannot_copy_files_from_one_directory_to_another( + return Err(EngineError::new_cannot_copy_files_from_one_directory_to_another( event_details.clone(), service.terraform_resource_dir_path(), workspace_dir.to_string(), e, - ) - .to_legacy_engine_error()); + )); } let external_svc_dir = format!("{}/{}", workspace_dir, "external-name-svc"); @@ -651,38 +658,35 @@ where external_svc_dir.as_str(), context.clone(), ) { - return Err(NewEngineError::new_cannot_copy_files_from_one_directory_to_another( + return Err(EngineError::new_cannot_copy_files_from_one_directory_to_another( event_details.clone(), service.helm_chart_external_name_service_dir(), external_svc_dir.to_string(), e, - ) - .to_legacy_engine_error()); + )); } let _ = crate::cmd::terraform::terraform_init_validate_plan_apply( workspace_dir.as_str(), service.context().is_dry_run_deploy(), ) - .map_err(|e| { - NewEngineError::new_terraform_error_while_executing_pipeline(event_details.clone(), e) - .to_legacy_engine_error() - })?; + .map_err(|e| EngineError::new_terraform_error_while_executing_pipeline(event_details.clone(), e))?; } else { // use helm - info!( - "deploy {} with name {} on {:?} Kubernetes cluster id {}", - service.service_type().name(), - service.name_with_id(), - kubernetes.cloud_provider().kind().to_string(), - kubernetes.id() + logger.log( + LogLevel::Info, + EngineEvent::Deploying( + event_details.clone(), + EventMessage::new_from_safe(format!( + "Deploying containerized {} `{}` on Kubernetes cluster", + service.service_type().name(), + service.name_with_id() + )), + ), ); let context = service.tera_context(target)?; - let kubernetes_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(path) => path, - Err(e) => return Err(e.to_legacy_engine_error()), - }; + let kubernetes_config_file_path = kubernetes.get_kubeconfig_file_path()?; // default chart if let Err(e) = crate::template::generate_and_copy_all_files_into_dir( @@ -690,13 +694,12 @@ where workspace_dir.as_str(), context.clone(), ) { - return Err(NewEngineError::new_cannot_copy_files_from_one_directory_to_another( + return Err(EngineError::new_cannot_copy_files_from_one_directory_to_another( event_details.clone(), service.helm_chart_dir(), workspace_dir.to_string(), e, - ) - .to_legacy_engine_error()); + )); } // overwrite with our chart values @@ -705,13 +708,12 @@ where workspace_dir.as_str(), context.clone(), ) { - return Err(NewEngineError::new_cannot_copy_files_from_one_directory_to_another( + return Err(EngineError::new_cannot_copy_files_from_one_directory_to_another( event_details.clone(), service.helm_chart_values_dir(), workspace_dir.to_string(), e, - ) - .to_legacy_engine_error()); + )); } // define labels to add to namespace @@ -732,8 +734,7 @@ where kubernetes.cloud_provider().credentials_environment_variables(), ) .map_err(|e| { - NewEngineError::new_k8s_create_namespace(event_details.clone(), environment.namespace().to_string(), e) - .to_legacy_engine_error() + EngineError::new_k8s_create_namespace(event_details.clone(), environment.namespace().to_string(), e) })?; // do exec helm upgrade and return the last deployment status @@ -741,7 +742,7 @@ where &kubernetes_config_file_path, &kubernetes.cloud_provider().credentials_environment_variables(), ) - .map_err(|e| helm::to_engine_error(&event_details, e).to_legacy_engine_error())?; + .map_err(|e| helm::to_engine_error(&event_details, e))?; let chart = ChartInfo::new_from_custom_namespace( service.helm_release_name(), workspace_dir.clone(), @@ -756,27 +757,28 @@ where ); helm.upgrade(&chart, &vec![]) - .map_err(|e| helm::to_engine_error(&event_details, e).to_legacy_engine_error())?; + .map_err(|e| helm::to_engine_error(&event_details, e))?; // check app status - match crate::cmd::kubectl::kubectl_exec_is_pod_ready_with_retry( + let is_pod_ready = crate::cmd::kubectl::kubectl_exec_is_pod_ready_with_retry( &kubernetes_config_file_path, environment.namespace(), service.selector().unwrap_or("".to_string()).as_str(), kubernetes.cloud_provider().credentials_environment_variables(), - ) { - Ok(Some(true)) => {} - _ => { - return Err(service.engine_error( - EngineErrorCause::Internal, - format!( - "{} database {} failed to start after several retries", - service.service_type().name(), - service.name_with_id() - ), - )); - } + ); + if let Ok(Some(true)) = is_pod_ready { + return Ok(()); } + + return Err(EngineError::new_database_failed_to_start_after_several_retries( + event_details.clone(), + service.name_with_id(), + service.service_type().name(), + match is_pod_ready { + Err(e) => Some(e), + _ => None, + }, + )); } Ok(()) @@ -786,6 +788,7 @@ pub fn delete_stateful_service( target: &DeploymentTarget, service: &T, event_details: EventDetails, + logger: &dyn Logger, ) -> Result<(), EngineError> where T: StatefulService + Helm + Terraform, @@ -801,13 +804,12 @@ where workspace_dir.as_str(), tera_context.clone(), ) { - return Err(NewEngineError::new_cannot_copy_files_from_one_directory_to_another( + return Err(EngineError::new_cannot_copy_files_from_one_directory_to_another( event_details.clone(), service.terraform_common_resource_dir_path(), workspace_dir.to_string(), e, - ) - .to_legacy_engine_error()); + )); } if let Err(e) = crate::template::generate_and_copy_all_files_into_dir( @@ -815,13 +817,12 @@ where workspace_dir.as_str(), tera_context.clone(), ) { - return Err(NewEngineError::new_cannot_copy_files_from_one_directory_to_another( + return Err(EngineError::new_cannot_copy_files_from_one_directory_to_another( event_details.clone(), service.terraform_resource_dir_path(), workspace_dir.to_string(), e, - ) - .to_legacy_engine_error()); + )); } let external_svc_dir = format!("{}/{}", workspace_dir, "external-name-svc"); @@ -830,13 +831,12 @@ where external_svc_dir.to_string(), tera_context.clone(), ) { - return Err(NewEngineError::new_cannot_copy_files_from_one_directory_to_another( + return Err(EngineError::new_cannot_copy_files_from_one_directory_to_another( event_details.clone(), service.helm_chart_external_name_service_dir(), external_svc_dir.to_string(), e, - ) - .to_legacy_engine_error()); + )); } if let Err(e) = crate::template::generate_and_copy_all_files_into_dir( @@ -844,26 +844,33 @@ where workspace_dir.as_str(), tera_context.clone(), ) { - return Err(NewEngineError::new_cannot_copy_files_from_one_directory_to_another( + return Err(EngineError::new_cannot_copy_files_from_one_directory_to_another( event_details.clone(), service.helm_chart_external_name_service_dir(), workspace_dir.to_string(), e, - ) - .to_legacy_engine_error()); + )); } match crate::cmd::terraform::terraform_init_validate_destroy(workspace_dir.as_str(), true) { Ok(_) => { - info!("deleting secret containing tfstates"); + logger.log( + LogLevel::Info, + EngineEvent::Deleting( + event_details.clone(), + EventMessage::new_from_safe("Deleting secret containing tfstates".to_string()), + ), + ); let _ = delete_terraform_tfstate_secret(kubernetes, environment.namespace(), &get_tfstate_name(service)); } Err(e) => { - let message = format!("{:?}", e); - error!("{}", message); + let engine_err = + EngineError::new_terraform_error_while_executing_destroy_pipeline(event_details.clone(), e); - return Err(service.engine_error(EngineErrorCause::Internal, message)); + logger.log(LogLevel::Error, EngineEvent::Error(engine_err.clone(), None)); + + return Err(engine_err); } } } else { @@ -881,7 +888,40 @@ where Ok(()) } -pub fn check_service_version(result: Result, service: &T) -> Result +pub struct ServiceVersionCheckResult { + requested_version: VersionsNumber, + matched_version: VersionsNumber, + message: Option, +} + +impl ServiceVersionCheckResult { + pub fn new(requested_version: VersionsNumber, matched_version: VersionsNumber, message: Option) -> Self { + ServiceVersionCheckResult { + requested_version, + matched_version, + message, + } + } + + pub fn matched_version(&self) -> VersionsNumber { + self.matched_version.clone() + } + + pub fn requested_version(&self) -> &VersionsNumber { + &self.requested_version + } + + pub fn message(&self) -> Option { + self.message.clone() + } +} + +pub fn check_service_version( + result: Result, + service: &T, + event_details: EventDetails, + logger: &dyn Logger, +) -> Result where T: Service + Listen, { @@ -891,35 +931,54 @@ where Ok(version) => { if service.version() != version.as_str() { let message = format!( - "{} version {} has been requested by the user; but matching version is {}", + "{} version `{}` has been requested by the user; but matching version is `{}`", service.service_type().name(), service.version(), version.as_str() ); - info!("{}", message.as_str()); + logger.log( + LogLevel::Info, + EngineEvent::Info(event_details.clone(), EventMessage::new_from_safe(message.to_string())), + ); let progress_info = ProgressInfo::new( service.progress_scope(), ProgressLevel::Info, - Some(message), + Some(message.to_string()), service.context().execution_id(), ); listeners_helper.deployment_in_progress(progress_info); + + return Ok(ServiceVersionCheckResult::new( + VersionsNumber::from_str(&service.version()).map_err(|e| { + EngineError::new_version_number_parsing_error(event_details.clone(), service.version(), e) + })?, + VersionsNumber::from_str(&version.to_string()).map_err(|e| { + EngineError::new_version_number_parsing_error(event_details.clone(), version.to_string(), e) + })?, + Some(message.to_string()), + )); } - Ok(version) + Ok(ServiceVersionCheckResult::new( + VersionsNumber::from_str(&service.version()).map_err(|e| { + EngineError::new_version_number_parsing_error(event_details.clone(), service.version(), e) + })?, + VersionsNumber::from_str(&version.to_string()).map_err(|e| { + EngineError::new_version_number_parsing_error(event_details.clone(), version.to_string(), e) + })?, + None, + )) } - Err(err) => { + Err(_err) => { let message = format!( "{} version {} is not supported!", service.service_type().name(), service.version(), ); - error!("{}", message.as_str()); - let progress_info = ProgressInfo::new( service.progress_scope(), ProgressLevel::Error, @@ -929,15 +988,15 @@ where listeners_helper.deployment_error(progress_info); - error!("{}", err); + let error = EngineError::new_unsupported_version_error( + event_details.clone(), + service.service_type().name(), + service.version(), + ); - Err(service.engine_error( - EngineErrorCause::User( - "The provided database version is not supported, please refer to the \ - documentation https://docs.qovery.com", - ), - err, - )) + logger.log(LogLevel::Error, EngineEvent::Error(error.clone(), None)); + + Err(error) } } } @@ -947,10 +1006,7 @@ fn delete_terraform_tfstate_secret( namespace: &str, secret_name: &str, ) -> Result<(), EngineError> { - let config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(path) => path, - Err(e) => return Err(e.to_legacy_engine_error()), - }; + let config_file_path = kubernetes.get_kubeconfig_file_path()?; // create the namespace to insert the tfstate in secrets let _ = kubectl_exec_delete_secret( @@ -974,43 +1030,55 @@ pub fn check_kubernetes_service_error( kubernetes: &dyn Kubernetes, service: &Box, event_details: EventDetails, + logger: &dyn Logger, deployment_target: &DeploymentTarget, listeners_helper: &ListenersHelper, action_verb: &str, action: CheckAction, -) -> Result<(), NewEngineError> +) -> Result<(), EngineError> where T: Service + ?Sized, { + let message = format!( + "{} {} {}", + action_verb, + service.service_type().name().to_lowercase(), + service.name() + ); + let progress_info = ProgressInfo::new( service.progress_scope(), ProgressLevel::Info, - Some(format!( - "{} {} {}", - action_verb, - service.service_type().name().to_lowercase(), - service.name() - )), + Some(message.to_string()), kubernetes.context().execution_id(), ); match action { - CheckAction::Deploy => listeners_helper.deployment_in_progress(progress_info), - CheckAction::Pause => listeners_helper.pause_in_progress(progress_info), - CheckAction::Delete => listeners_helper.delete_in_progress(progress_info), + CheckAction::Deploy => { + listeners_helper.deployment_in_progress(progress_info); + logger.log( + LogLevel::Info, + EngineEvent::Deploying(event_details.clone(), EventMessage::new_from_safe(message.to_string())), + ); + } + CheckAction::Pause => { + listeners_helper.pause_in_progress(progress_info); + logger.log( + LogLevel::Info, + EngineEvent::Pausing(event_details.clone(), EventMessage::new_from_safe(message.to_string())), + ); + } + CheckAction::Delete => { + listeners_helper.delete_in_progress(progress_info); + logger.log( + LogLevel::Info, + EngineEvent::Deleting(event_details.clone(), EventMessage::new_from_safe(message.to_string())), + ); + } } match result { Err(err) => { - error!( - "{} error with {} {} , id: {} => {:?}", - action_verb, - service.service_type().name(), - service.name(), - service.id(), - err - ); - let progress_info = ProgressInfo::new( service.progress_scope(), ProgressLevel::Error, @@ -1024,26 +1092,18 @@ where kubernetes.context().execution_id(), ); - match action { - CheckAction::Deploy => listeners_helper.deployment_error(progress_info), - CheckAction::Pause => listeners_helper.pause_error(progress_info), - CheckAction::Delete => listeners_helper.delete_error(progress_info), - } - - let debug_logs = service.debug_logs(deployment_target, event_details.clone()); - let debug_logs_string = if !debug_logs.is_empty() { - debug_logs.join("\n") - } else { - String::from("") - }; - - info!("{}", debug_logs_string); - - let progress_info = ProgressInfo::new( - service.progress_scope(), - ProgressLevel::Debug, - Some(debug_logs_string), - kubernetes.context().execution_id(), + logger.log( + LogLevel::Error, + EngineEvent::Error( + err.clone(), + Some(EventMessage::new_from_safe(format!( + "{} error with {} {} , id: {}", + action_verb, + service.service_type().name(), + service.name(), + service.id(), + ))), + ), ); match action { @@ -1052,9 +1112,34 @@ where CheckAction::Delete => listeners_helper.delete_error(progress_info), } - return Err(NewEngineError::new_k8s_service_issue( + let debug_logs = service.debug_logs(deployment_target, event_details.clone(), logger); + let debug_logs_string = if !debug_logs.is_empty() { + debug_logs.join("\n") + } else { + String::from("") + }; + + let progress_info = ProgressInfo::new( + service.progress_scope(), + ProgressLevel::Debug, + Some(debug_logs_string.to_string()), + kubernetes.context().execution_id(), + ); + + logger.log( + LogLevel::Debug, + EngineEvent::Debug(event_details.clone(), EventMessage::new_from_safe(debug_logs_string)), + ); + + match action { + CheckAction::Deploy => listeners_helper.deployment_error(progress_info), + CheckAction::Pause => listeners_helper.pause_error(progress_info), + CheckAction::Delete => listeners_helper.delete_error(progress_info), + } + + return Err(EngineError::new_k8s_service_issue( event_details.clone(), - CommandError::new(err.message.unwrap_or("No error message.".to_string()), None), + CommandError::new(err.message(), Some("Error with Kubernetes service".to_string())), )); } _ => { @@ -1097,10 +1182,7 @@ where { let selector = service.selector().unwrap_or("".to_string()); let mut result = Vec::with_capacity(50); - let kubernetes_config_file_path = match kubernetes.get_kubeconfig_file_path() { - Ok(path) => path, - Err(e) => return Err(e.to_legacy_engine_error()), - }; + let kubernetes_config_file_path = kubernetes.get_kubeconfig_file_path()?; // get logs let logs = crate::cmd::kubectl::kubectl_exec_logs( @@ -1110,13 +1192,12 @@ where kubernetes.cloud_provider().credentials_environment_variables(), ) .map_err(|e| { - NewEngineError::new_k8s_get_logs_error( + EngineError::new_k8s_get_logs_error( event_details.clone(), selector.to_string(), environment.namespace().to_string(), e, ) - .to_legacy_engine_error() })?; let _ = result.extend(logs); @@ -1128,7 +1209,7 @@ where Some(selector.as_str()), kubernetes.cloud_provider().credentials_environment_variables(), ) - .map_err(|e| NewEngineError::new_k8s_cannot_get_pods(event_details.clone(), e).to_legacy_engine_error())? + .map_err(|e| EngineError::new_k8s_cannot_get_pods(event_details.clone(), e))? .items; for pod in pods { @@ -1167,10 +1248,7 @@ where environment.namespace(), kubernetes.cloud_provider().credentials_environment_variables(), ) - .map_err(|e| { - NewEngineError::new_k8s_get_json_events(event_details.clone(), environment.namespace().to_string(), e) - .to_legacy_engine_error() - })? + .map_err(|e| EngineError::new_k8s_get_json_events(event_details.clone(), environment.namespace().to_string(), e))? .items; for event in events { @@ -1198,9 +1276,7 @@ pub fn get_stateless_resource_information( stage: Stage, ) -> Result<(Describe, Logs), EngineError> { let event_details = kubernetes.get_event_details(stage); - let kubernetes_config_file_path = kubernetes - .get_kubeconfig_file_path() - .map_err(|e| e.to_legacy_engine_error())?; + let kubernetes_config_file_path = kubernetes.get_kubeconfig_file_path()?; // exec describe pod... let describe = crate::cmd::kubectl::kubectl_exec_describe_pod( @@ -1210,13 +1286,12 @@ pub fn get_stateless_resource_information( kubernetes.cloud_provider().credentials_environment_variables(), ) .map_err(|e| { - NewEngineError::new_k8s_describe( + EngineError::new_k8s_describe( event_details.clone(), selector.to_string(), environment.namespace().to_string(), e, ) - .to_legacy_engine_error() })?; // exec logs... @@ -1227,13 +1302,12 @@ pub fn get_stateless_resource_information( kubernetes.cloud_provider().credentials_environment_variables(), ) .map_err(|e| { - NewEngineError::new_k8s_get_logs_error( + EngineError::new_k8s_get_logs_error( event_details.clone(), selector.to_string(), environment.namespace().to_string(), e, ) - .to_legacy_engine_error() })? .join("\n"); @@ -1246,19 +1320,17 @@ pub fn helm_uninstall_release( helm_release_name: &str, event_details: EventDetails, ) -> Result<(), EngineError> { - let kubernetes_config_file_path = kubernetes - .get_kubeconfig_file_path() - .map_err(|e| e.to_legacy_engine_error())?; + let kubernetes_config_file_path = kubernetes.get_kubeconfig_file_path()?; let helm = cmd::helm::Helm::new( &kubernetes_config_file_path, &kubernetes.cloud_provider().credentials_environment_variables(), ) - .map_err(|e| NewEngineError::new_helm_error(event_details.clone(), e).to_legacy_engine_error())?; + .map_err(|e| EngineError::new_helm_error(event_details.clone(), e))?; let chart = ChartInfo::new_from_release_name(helm_release_name, environment.namespace()); helm.uninstall(&chart, &vec![]) - .map_err(|e| NewEngineError::new_helm_error(event_details.clone(), e).to_legacy_engine_error()) + .map_err(|e| EngineError::new_helm_error(event_details.clone(), e)) } /// This function call (start|pause|delete)_in_progress function every 10 seconds when a @@ -1292,23 +1364,26 @@ where /// This function call (start|pause|delete)_in_progress function every 10 seconds when a /// long blocking task is running. -pub fn send_progress_on_long_task_with_message( +pub fn send_progress_on_long_task_with_message( service: &S, - waiting_message: Option, + waiting_message: Option, action: Action, long_task: F, ) -> R where S: Service + Listen, - M: Into, F: Fn() -> R, { + let event_details = service + .get_event_details(Stage::Environment(EnvironmentStep::Deploy)) + .clone(); + let logger = service.logger().clone_dyn(); let listeners = std::clone::Clone::clone(service.listeners()); let progress_info = ProgressInfo::new( service.progress_scope(), Info, - waiting_message.map(|message| message.into()), + waiting_message.clone(), service.context().execution_id(), ); @@ -1322,15 +1397,54 @@ where let listeners_helper = ListenersHelper::new(&listeners); let action = action; let progress_info = progress_info; + let waiting_message = waiting_message.clone().unwrap_or("No message...".to_string()); loop { // do notify users here let progress_info = std::clone::Clone::clone(&progress_info); + let event_details = std::clone::Clone::clone(&event_details); + let event_message = EventMessage::new_from_safe(waiting_message.to_string()); match action { - Action::Create => listeners_helper.deployment_in_progress(progress_info), - Action::Pause => listeners_helper.pause_in_progress(progress_info), - Action::Delete => listeners_helper.delete_in_progress(progress_info), + Action::Create => { + listeners_helper.deployment_in_progress(progress_info); + logger.log( + LogLevel::Info, + EngineEvent::Deploying( + EventDetails::clone_changing_stage( + event_details, + Stage::Environment(EnvironmentStep::Deploy), + ), + event_message, + ), + ); + } + Action::Pause => { + listeners_helper.pause_in_progress(progress_info); + logger.log( + LogLevel::Info, + EngineEvent::Pausing( + EventDetails::clone_changing_stage( + event_details, + Stage::Environment(EnvironmentStep::Pause), + ), + event_message, + ), + ); + } + Action::Delete => { + listeners_helper.delete_in_progress(progress_info); + logger.log( + LogLevel::Info, + EngineEvent::Deleting( + EventDetails::clone_changing_stage( + event_details, + Stage::Environment(EnvironmentStep::Delete), + ), + event_message, + ), + ); + } Action::Nothing => {} // should not happens }; diff --git a/src/cloud_provider/utilities.rs b/src/cloud_provider/utilities.rs index 1faef024..97a74f99 100644 --- a/src/cloud_provider/utilities.rs +++ b/src/cloud_provider/utilities.rs @@ -1,7 +1,8 @@ use std::collections::HashMap; -use crate::cloud_provider::models::CpuLimits; -use crate::error::{EngineError, StringError}; +use crate::errors::{CommandError, EngineError}; +use crate::events::{EngineEvent, EventDetails, EventMessage}; +use crate::logger::{LogLevel, Logger}; use crate::models::{Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope}; use chrono::Duration; use core::option::Option::{None, Some}; @@ -11,13 +12,12 @@ use retry::delay::Fixed; use retry::OperationResult; use serde::{Deserialize, Serialize}; use std::fmt; -use std::num::ParseFloatError; use std::str::FromStr; use trust_dns_resolver::config::*; use trust_dns_resolver::proto::rr::{RData, RecordType}; use trust_dns_resolver::Resolver; -pub fn get_self_hosted_postgres_version(requested_version: String) -> Result { +pub fn get_self_hosted_postgres_version(requested_version: String) -> Result { let mut supported_postgres_versions = HashMap::new(); // https://hub.docker.com/r/bitnami/postgresql/tags?page=1&ordering=last_updated @@ -41,7 +41,7 @@ pub fn get_self_hosted_postgres_version(requested_version: String) -> Result Result { +pub fn get_self_hosted_mysql_version(requested_version: String) -> Result { let mut supported_mysql_versions = HashMap::new(); // https://hub.docker.com/r/bitnami/mysql/tags?page=1&ordering=last_updated @@ -56,7 +56,7 @@ pub fn get_self_hosted_mysql_version(requested_version: String) -> Result Result { +pub fn get_self_hosted_mongodb_version(requested_version: String) -> Result { let mut supported_mongodb_versions = HashMap::new(); // https://hub.docker.com/r/bitnami/mongodb/tags?page=1&ordering=last_updated @@ -80,7 +80,7 @@ pub fn get_self_hosted_mongodb_version(requested_version: String) -> Result Result { +pub fn get_self_hosted_redis_version(requested_version: String) -> Result { let mut supported_redis_versions = HashMap::with_capacity(4); // https://hub.docker.com/r/bitnami/redis/tags?page=1&ordering=last_updated @@ -96,11 +96,8 @@ pub fn get_supported_version_to_use( database_name: &str, all_supported_versions: HashMap, version_to_check: String, -) -> Result { - let version = match VersionsNumber::from_str(version_to_check.as_str()) { - Ok(version) => version, - Err(e) => return Err(e), - }; +) -> Result { + let version = VersionsNumber::from_str(version_to_check.as_str())?; // if a patch version is required if version.patch.is_some() { @@ -112,10 +109,10 @@ pub fn get_supported_version_to_use( )) { Some(version) => Ok(version.to_string()), None => { - return Err(format!( + return Err(CommandError::new_from_safe_message(format!( "{} {} version is not supported", database_name, version_to_check - )); + ))); } }; } @@ -125,10 +122,10 @@ pub fn get_supported_version_to_use( return match all_supported_versions.get(&format!("{}.{}", version.major, version.minor.unwrap())) { Some(version) => Ok(version.to_string()), None => { - return Err(format!( + return Err(CommandError::new_from_safe_message(format!( "{} {} version is not supported", database_name, version_to_check - )); + ))); } }; }; @@ -137,10 +134,10 @@ pub fn get_supported_version_to_use( match all_supported_versions.get(&version.major) { Some(version) => Ok(version.to_string()), None => { - return Err(format!( + return Err(CommandError::new_from_safe_message(format!( "{} {} version is not supported", database_name, version_to_check - )); + ))); } } } @@ -275,11 +272,13 @@ impl VersionsNumber { } impl FromStr for VersionsNumber { - type Err = StringError; + type Err = CommandError; fn from_str(version: &str) -> Result { if version.trim() == "" { - return Err(StringError::from("version cannot be empty")); + return Err(CommandError::new_from_safe_message( + "version cannot be empty".to_string(), + )); } let mut version_split = version.splitn(4, '.').map(|v| v.trim()); @@ -290,10 +289,10 @@ impl FromStr for VersionsNumber { major.replace("v", "") } None => { - return Err(format!( + return Err(CommandError::new_from_safe_message(format!( "please check the version you've sent ({}), it can't be checked", version - )) + ))) } }; @@ -438,19 +437,23 @@ pub fn check_domain_for( domains_to_check: Vec<&str>, execution_id: &str, context_id: &str, + event_details: EventDetails, + logger: &dyn Logger, ) -> Result<(), EngineError> { let resolvers = dns_resolvers(); for domain in domains_to_check { + let message = format!( + "Let's check domain resolution for '{}'. Please wait, it can take some time...", + domain + ); + listener_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Environment { id: execution_id.to_string(), }, ProgressLevel::Info, - Some(format!( - "Let's check domain resolution for '{}'. Please wait, it can take some time...", - domain - )), + Some(message.to_string()), execution_id, )); @@ -460,13 +463,22 @@ pub fn check_domain_for( ix += 1; resolver }; + + logger.log( + LogLevel::Info, + EngineEvent::Info(event_details.clone(), EventMessage::new_from_safe(message.to_string())), + ); + let fixed_iterable = Fixed::from_millis(3000).take(100); let check_result = retry::retry(fixed_iterable, || match next_resolver().lookup_ip(domain) { Ok(lookup_ip) => OperationResult::Ok(lookup_ip), Err(err) => { let x = format!("Domain resolution check for '{}' is still in progress...", domain); - info!("{}", x); + logger.log( + LogLevel::Info, + EngineEvent::Info(event_details.clone(), EventMessage::new_from_safe(x.to_string())), + ); listener_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Environment { @@ -485,7 +497,10 @@ pub fn check_domain_for( Ok(_) => { let x = format!("Domain {} is ready! ⚡️", domain); - info!("{}", x); + logger.log( + LogLevel::Info, + EngineEvent::Info(event_details.clone(), EventMessage::new_from_safe(message.to_string())), + ); listener_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Environment { @@ -503,7 +518,10 @@ pub fn check_domain_for( domain ); - warn!("{}", message); + logger.log( + LogLevel::Warning, + EngineEvent::Warning(event_details.clone(), EventMessage::new_from_safe(message.to_string())), + ); listener_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Environment { @@ -533,122 +551,36 @@ pub fn managed_db_name_sanitizer(max_size: usize, prefix: &str, name: &str) -> S new_name } -pub fn convert_k8s_cpu_value_to_f32(value: String) -> Result { - if value.ends_with('m') { - let mut value_number_string = value; - value_number_string.pop(); - return match value_number_string.parse::() { - Ok(n) => { - Ok(n * 0.001) // return in milli cpu the value - } - Err(e) => Err(e), - }; - } - - match value.parse::() { - Ok(n) => Ok(n), - Err(e) => Err(e), - } -} - -pub fn validate_k8s_required_cpu_and_burstable( - listener_helper: &ListenersHelper, - execution_id: &str, - context_id: &str, - total_cpu: String, - cpu_burst: String, -) -> Result { - let total_cpu_float = convert_k8s_cpu_value_to_f32(total_cpu.clone())?; - let cpu_burst_float = convert_k8s_cpu_value_to_f32(cpu_burst.clone())?; - let mut set_cpu_burst = cpu_burst.clone(); - - if cpu_burst_float < total_cpu_float { - let message = format!( - "CPU burst value '{}' was lower than the desired total of CPUs {}, using burstable value. Please ensure your configuration is valid", - cpu_burst, - total_cpu, - ); - - warn!("{}", message); - - listener_helper.error(ProgressInfo::new( - ProgressScope::Environment { - id: execution_id.to_string(), - }, - ProgressLevel::Warn, - Some(message), - context_id, - )); - set_cpu_burst = total_cpu.clone(); - } - - Ok(CpuLimits { - cpu_limit: set_cpu_burst, - cpu_request: total_cpu, - }) -} - -pub fn print_action(cloud_provider_name: &str, struct_name: &str, fn_name: &str, item_name: &str) { +pub fn print_action( + cloud_provider_name: &str, + struct_name: &str, + fn_name: &str, + item_name: &str, + event_details: EventDetails, + logger: &dyn Logger, +) { let msg = format!( "{}.{}.{} called for {}", cloud_provider_name, struct_name, fn_name, item_name ); match fn_name.contains("error") { - true => warn!("{}", msg), - false => info!("{}", msg), + true => logger.log( + LogLevel::Warning, + EngineEvent::Warning(event_details, EventMessage::new_from_safe(msg)), + ), + false => logger.log( + LogLevel::Info, + EngineEvent::Info(event_details, EventMessage::new_from_safe(msg)), + ), } } #[cfg(test)] mod tests { - use crate::cloud_provider::models::CpuLimits; - use crate::cloud_provider::utilities::{ - convert_k8s_cpu_value_to_f32, dns_resolvers, get_cname_record_value, validate_k8s_required_cpu_and_burstable, - VersionsNumber, - }; - use crate::error::StringError; - use crate::models::ListenersHelper; + use crate::cloud_provider::utilities::{dns_resolvers, get_cname_record_value, VersionsNumber}; + use crate::errors::CommandError; use std::str::FromStr; - #[test] - pub fn test_k8s_milli_cpu_convert() { - let milli_cpu = "250m".to_string(); - let int_cpu = "2".to_string(); - - assert_eq!(convert_k8s_cpu_value_to_f32(milli_cpu).unwrap(), 0.25 as f32); - assert_eq!(convert_k8s_cpu_value_to_f32(int_cpu).unwrap(), 2 as f32); - } - - #[test] - pub fn test_cpu_set() { - let v = vec![]; - let listener_helper = ListenersHelper::new(&v); - let execution_id = "execution_id"; - let context_id = "context_id"; - - let mut total_cpu = "0.25".to_string(); - let mut cpu_burst = "1".to_string(); - assert_eq!( - validate_k8s_required_cpu_and_burstable(&listener_helper, execution_id, context_id, total_cpu, cpu_burst) - .unwrap(), - CpuLimits { - cpu_request: "0.25".to_string(), - cpu_limit: "1".to_string() - } - ); - - total_cpu = "1".to_string(); - cpu_burst = "0.5".to_string(); - assert_eq!( - validate_k8s_required_cpu_and_burstable(&listener_helper, execution_id, context_id, total_cpu, cpu_burst) - .unwrap(), - CpuLimits { - cpu_request: "1".to_string(), - cpu_limit: "1".to_string() - } - ); - } - #[test] pub fn test_cname_resolution() { let resolvers = dns_resolvers(); @@ -662,24 +594,30 @@ mod tests { // setup: struct TestCase<'a> { input: &'a str, - expected_output: Result, + expected_output: Result, description: &'a str, } let test_cases = vec![ TestCase { input: "", - expected_output: Err(StringError::from("version cannot be empty")), + expected_output: Err(CommandError::new_from_safe_message( + "version cannot be empty".to_string(), + )), description: "empty version str", }, TestCase { input: " ", - expected_output: Err(StringError::from("version cannot be empty")), + expected_output: Err(CommandError::new_from_safe_message( + "version cannot be empty".to_string(), + )), description: "version a tab str", }, TestCase { input: " ", - expected_output: Err(StringError::from("version cannot be empty")), + expected_output: Err(CommandError::new_from_safe_message( + "version cannot be empty".to_string(), + )), description: "version as a space str", }, TestCase { diff --git a/src/container_registry/docker_hub.rs b/src/container_registry/docker_hub.rs index be984083..8dfa22c3 100644 --- a/src/container_registry/docker_hub.rs +++ b/src/container_registry/docker_hub.rs @@ -7,6 +7,8 @@ use crate::cmd::command::QoveryCommand; use crate::container_registry::docker::{docker_pull_image, docker_tag_and_push_image}; use crate::container_registry::{ContainerRegistry, EngineError, Kind, PullResult, PushResult}; use crate::error::EngineErrorCause; +use crate::errors::EngineError as NewEngineError; +use crate::events::{ToTransmitter, Transmitter}; use crate::models::{ Context, Listen, Listener, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope, }; @@ -72,6 +74,12 @@ impl DockerHub { } } +impl ToTransmitter for DockerHub { + fn to_transmitter(&self) -> Transmitter { + Transmitter::ContainerRegistry(self.id().to_string(), self.name().to_string()) + } +} + impl ContainerRegistry for DockerHub { fn context(&self) -> &Context { &self.context @@ -89,16 +97,7 @@ impl ContainerRegistry for DockerHub { self.name.as_str() } - fn is_valid(&self) -> Result<(), EngineError> { - // check the version of docker and print it as info - let mut output_from_cmd = String::new(); - let mut cmd = QoveryCommand::new("docker", &vec!["--version"], &vec![]); - let _ = cmd.exec_with_output( - |r_out| output_from_cmd.push_str(&r_out), - |r_err| error!("Error executing docker command {}", r_err), - ); - - info!("Using Docker: {}", output_from_cmd); + fn is_valid(&self) -> Result<(), NewEngineError> { Ok(()) } diff --git a/src/container_registry/docr.rs b/src/container_registry/docr.rs index e3121bd4..cfb329e3 100644 --- a/src/container_registry/docr.rs +++ b/src/container_registry/docr.rs @@ -8,6 +8,8 @@ use crate::cmd::command::QoveryCommand; use crate::container_registry::docker::{docker_pull_image, docker_tag_and_push_image}; use crate::container_registry::{ContainerRegistry, EngineError, Kind, PullResult, PushResult}; use crate::error::{cast_simple_error_to_engine_error, EngineErrorCause, SimpleError, SimpleErrorKind}; +use crate::errors::EngineError as NewEngineError; +use crate::events::{ToTransmitter, Transmitter}; use crate::models::{ Context, Listen, Listener, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope, }; @@ -228,6 +230,12 @@ impl DOCR { } } +impl ToTransmitter for DOCR { + fn to_transmitter(&self) -> Transmitter { + Transmitter::ContainerRegistry(self.id().to_string(), self.name().to_string()) + } +} + impl ContainerRegistry for DOCR { fn context(&self) -> &Context { &self.context @@ -245,7 +253,7 @@ impl ContainerRegistry for DOCR { self.name.as_str() } - fn is_valid(&self) -> Result<(), EngineError> { + fn is_valid(&self) -> Result<(), NewEngineError> { Ok(()) } diff --git a/src/container_registry/ecr.rs b/src/container_registry/ecr.rs index e33aa69e..478903c3 100644 --- a/src/container_registry/ecr.rs +++ b/src/container_registry/ecr.rs @@ -13,6 +13,8 @@ use crate::cmd::command::QoveryCommand; use crate::container_registry::docker::{docker_pull_image, docker_tag_and_push_image}; use crate::container_registry::{ContainerRegistry, Kind, PullResult, PushResult}; use crate::error::{EngineError, EngineErrorCause}; +use crate::errors::EngineError as NewEngineError; +use crate::events::{ToTransmitter, Transmitter}; use crate::models::{ Context, Listen, Listener, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope, }; @@ -359,6 +361,12 @@ impl ECR { } } +impl ToTransmitter for ECR { + fn to_transmitter(&self) -> Transmitter { + Transmitter::ContainerRegistry(self.id().to_string(), self.name().to_string()) + } +} + impl ContainerRegistry for ECR { fn context(&self) -> &Context { &self.context @@ -376,18 +384,14 @@ impl ContainerRegistry for ECR { self.name.as_str() } - fn is_valid(&self) -> Result<(), EngineError> { + fn is_valid(&self) -> Result<(), NewEngineError> { let client = StsClient::new_with_client(self.client(), Region::default()); let s = block_on(client.get_caller_identity(GetCallerIdentityRequest::default())); match s { 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()), + Err(_) => Err(NewEngineError::new_client_invalid_cloud_provider_credentials( + self.get_event_details(), )), } } diff --git a/src/container_registry/mod.rs b/src/container_registry/mod.rs index e9c3950e..7a9bdea6 100644 --- a/src/container_registry/mod.rs +++ b/src/container_registry/mod.rs @@ -2,7 +2,9 @@ use serde::{Deserialize, Serialize}; use crate::build_platform::Image; use crate::error::{EngineError, EngineErrorCause, EngineErrorScope}; -use crate::models::{Context, Listen}; +use crate::errors::EngineError as NewEngineError; +use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter}; +use crate::models::{Context, Listen, QoveryIdentifier}; pub mod docker; pub mod docker_hub; @@ -10,7 +12,7 @@ pub mod docr; pub mod ecr; pub mod scaleway_container_registry; -pub trait ContainerRegistry: Listen { +pub trait ContainerRegistry: Listen + ToTransmitter { fn context(&self) -> &Context; fn kind(&self) -> Kind; fn id(&self) -> &str; @@ -18,7 +20,7 @@ pub trait ContainerRegistry: Listen { fn name_with_id(&self) -> String { format!("{} ({})", self.name(), self.id()) } - fn is_valid(&self) -> Result<(), EngineError>; + fn is_valid(&self) -> Result<(), NewEngineError>; fn on_create(&self) -> Result<(), EngineError>; fn on_create_error(&self) -> Result<(), EngineError>; fn on_delete(&self) -> Result<(), EngineError>; @@ -38,6 +40,18 @@ pub trait ContainerRegistry: Listen { Some(message), ) } + fn get_event_details(&self) -> EventDetails { + let context = self.context(); + EventDetails::new( + None, + QoveryIdentifier::from(context.organization_id().to_string()), + QoveryIdentifier::from(context.cluster_id().to_string()), + QoveryIdentifier::from(context.execution_id().to_string()), + None, + Stage::Environment(EnvironmentStep::Build), + self.to_transmitter(), + ) + } } pub struct PushResult { diff --git a/src/container_registry/scaleway_container_registry.rs b/src/container_registry/scaleway_container_registry.rs index ecaf109c..0acdb802 100644 --- a/src/container_registry/scaleway_container_registry.rs +++ b/src/container_registry/scaleway_container_registry.rs @@ -9,6 +9,8 @@ use crate::container_registry::docker::{ }; use crate::container_registry::{ContainerRegistry, Kind, PullResult, PushResult}; use crate::error::{EngineError, EngineErrorCause}; +use crate::errors::EngineError as NewEngineError; +use crate::events::{ToTransmitter, Transmitter}; use crate::models::{ Context, Listen, Listener, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope, }; @@ -341,6 +343,12 @@ impl ScalewayCR { } } +impl ToTransmitter for ScalewayCR { + fn to_transmitter(&self) -> Transmitter { + Transmitter::ContainerRegistry(self.id().to_string(), self.name().to_string()) + } +} + impl ContainerRegistry for ScalewayCR { fn context(&self) -> &Context { &self.context @@ -358,7 +366,7 @@ impl ContainerRegistry for ScalewayCR { self.name.as_str() } - fn is_valid(&self) -> Result<(), EngineError> { + fn is_valid(&self) -> Result<(), NewEngineError> { Ok(()) } diff --git a/src/dns_provider/cloudflare.rs b/src/dns_provider/cloudflare.rs index e93ba3cf..134c7b7a 100644 --- a/src/dns_provider/cloudflare.rs +++ b/src/dns_provider/cloudflare.rs @@ -1,7 +1,8 @@ use std::net::Ipv4Addr; use crate::dns_provider::{DnsProvider, Kind}; -use crate::error::{EngineError, EngineErrorCause}; +use crate::errors::EngineError; +use crate::events::{ToTransmitter, Transmitter}; use crate::models::{Context, Domain}; pub struct Cloudflare { @@ -72,15 +73,17 @@ impl DnsProvider for Cloudflare { fn is_valid(&self) -> Result<(), EngineError> { if self.cloudflare_api_token.is_empty() || self.cloudflare_email.is_empty() { - 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()), + Err(EngineError::new_client_invalid_cloud_provider_credentials( + self.get_event_details(), )) } else { Ok(()) } } } + +impl ToTransmitter for Cloudflare { + fn to_transmitter(&self) -> Transmitter { + Transmitter::DnsProvider(self.id().to_string(), self.name().to_string()) + } +} diff --git a/src/dns_provider/mod.rs b/src/dns_provider/mod.rs index 859199d2..7d140468 100644 --- a/src/dns_provider/mod.rs +++ b/src/dns_provider/mod.rs @@ -3,11 +3,13 @@ use std::net::Ipv4Addr; use serde::{Deserialize, Serialize}; use crate::error::{EngineError, EngineErrorCause, EngineErrorScope}; -use crate::models::{Context, Domain}; +use crate::errors::EngineError as NewEngineError; +use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter}; +use crate::models::{Context, Domain, QoveryIdentifier}; pub mod cloudflare; -pub trait DnsProvider { +pub trait DnsProvider: ToTransmitter { fn context(&self) -> &Context; fn provider_name(&self) -> &str; fn kind(&self) -> Kind; @@ -20,7 +22,7 @@ pub trait DnsProvider { fn token(&self) -> &str; fn domain(&self) -> &Domain; fn resolvers(&self) -> Vec; - fn is_valid(&self) -> Result<(), EngineError>; + fn is_valid(&self) -> Result<(), NewEngineError>; fn engine_error_scope(&self) -> EngineErrorScope { EngineErrorScope::DnsProvider(self.id().to_string(), self.name().to_string()) } @@ -32,6 +34,18 @@ pub trait DnsProvider { Some(message), ) } + fn get_event_details(&self) -> EventDetails { + let context = self.context(); + EventDetails::new( + None, + QoveryIdentifier::from(context.organization_id().to_string()), + QoveryIdentifier::from(context.cluster_id().to_string()), + QoveryIdentifier::from(context.execution_id().to_string()), + None, + Stage::Environment(EnvironmentStep::Deploy), + self.to_transmitter(), + ) + } } #[derive(Serialize, Deserialize, Clone, Debug)] diff --git a/src/engine.rs b/src/engine.rs index 26f10a05..6ebb6653 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -4,7 +4,7 @@ use crate::build_platform::BuildPlatform; use crate::cloud_provider::CloudProvider; use crate::container_registry::ContainerRegistry; use crate::dns_provider::DnsProvider; -use crate::error::EngineError; +use crate::errors::EngineError; use crate::logger::Logger; use crate::models::Context; use crate::session::Session; diff --git a/src/error.rs b/src/error.rs index 7af3c05c..a64601ab 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,7 @@ pub type Id = String; pub type Name = String; #[derive(Debug)] +#[deprecated(note = "errors.EngineError to be used instead")] pub struct EngineError { pub cause: EngineErrorCause, pub scope: EngineErrorScope, @@ -72,6 +73,7 @@ pub enum EngineErrorCause { } #[derive(Debug)] +#[deprecated(note = "errors.CommandError to be used instead")] pub struct SimpleError { pub kind: SimpleErrorKind, pub message: Option, diff --git a/src/errors/io.rs b/src/errors/io.rs index cb77a248..0cebdd46 100644 --- a/src/errors/io.rs +++ b/src/errors/io.rs @@ -79,6 +79,25 @@ pub enum Tag { NoClusterFound, OnlyOneClusterExpected, CloudProviderApiMissingInfo, + K8sValidateRequiredCPUandBurstableError, + TerraformContextUnsupportedParameterValue, + ClientServiceFailedToStart, + ClientServiceFailedToDeployBeforeStart, + DatabaseFailedToStartAfterSeveralRetries, + RouterFailedToDeploy, + CloudProviderClientInvalidCredentials, + VersionNumberParsingError, + NotImplementedError, + BuilderDockerCannotFindAnyDockerfile, + BuilderDockerCannotReadDockerfile, + BuilderDockerCannotExtractEnvVarsFromDockerfile, + BuilderDockerCannotBuildContainerImage, + BuilderBuildpackInvalidLanguageFormat, + BuilderBuildpackCannotBuildContainerImage, + BuilderGetBuildError, + BuilderCloningRepositoryError, + DockerPushImageError, + DockerPullImageError, } impl From for Tag { @@ -145,6 +164,28 @@ impl From for Tag { errors::Tag::NoClusterFound => Tag::NoClusterFound, errors::Tag::OnlyOneClusterExpected => Tag::OnlyOneClusterExpected, errors::Tag::CloudProviderApiMissingInfo => Tag::CloudProviderApiMissingInfo, + errors::Tag::K8sValidateRequiredCPUandBurstableError => Tag::K8sValidateRequiredCPUandBurstableError, + errors::Tag::TerraformContextUnsupportedParameterValue => Tag::TerraformContextUnsupportedParameterValue, + errors::Tag::ClientServiceFailedToStart => Tag::ClientServiceFailedToStart, + errors::Tag::ClientServiceFailedToDeployBeforeStart => Tag::ClientServiceFailedToDeployBeforeStart, + errors::Tag::DatabaseFailedToStartAfterSeveralRetries => Tag::DatabaseFailedToStartAfterSeveralRetries, + errors::Tag::RouterFailedToDeploy => Tag::RouterFailedToDeploy, + errors::Tag::CloudProviderClientInvalidCredentials => Tag::CloudProviderClientInvalidCredentials, + errors::Tag::VersionNumberParsingError => Tag::VersionNumberParsingError, + errors::Tag::NotImplementedError => Tag::NotImplementedError, + errors::Tag::TaskCancellationRequested => Tag::CannotPauseClusterTasksAreRunning, + errors::Tag::BuilderDockerCannotFindAnyDockerfile => Tag::BuilderDockerCannotFindAnyDockerfile, + errors::Tag::BuilderDockerCannotReadDockerfile => Tag::BuilderDockerCannotReadDockerfile, + errors::Tag::BuilderDockerCannotExtractEnvVarsFromDockerfile => { + Tag::BuilderDockerCannotExtractEnvVarsFromDockerfile + } + errors::Tag::BuilderDockerCannotBuildContainerImage => Tag::BuilderDockerCannotBuildContainerImage, + errors::Tag::BuilderBuildpackInvalidLanguageFormat => Tag::BuilderBuildpackInvalidLanguageFormat, + errors::Tag::BuilderBuildpackCannotBuildContainerImage => Tag::BuilderBuildpackCannotBuildContainerImage, + errors::Tag::BuilderGetBuildError => Tag::BuilderGetBuildError, + errors::Tag::BuilderCloningRepositoryError => Tag::BuilderCloningRepositoryError, + errors::Tag::DockerPushImageError => Tag::DockerPushImageError, + errors::Tag::DockerPullImageError => Tag::DockerPullImageError, } } } diff --git a/src/errors/mod.rs b/src/errors/mod.rs index 915dd366..b3b1a6a8 100644 --- a/src/errors/mod.rs +++ b/src/errors/mod.rs @@ -5,7 +5,8 @@ extern crate url; use crate::cloud_provider::utilities::VersionsNumber; use crate::cmd::helm::HelmError; use crate::error::{EngineError as LegacyEngineError, EngineErrorCause, EngineErrorScope}; -use crate::events::EventDetails; +use crate::events::{EventDetails, GeneralStep, Stage, Transmitter}; +use crate::models::QoveryIdentifier; use url::Url; /// CommandError: command error, mostly returned by third party tools. @@ -32,7 +33,10 @@ impl CommandError { pub fn message(&self) -> String { // TODO(benjaminch): To be revamped, not sure how we should deal with safe and unsafe messages. if let Some(msg) = &self.message_safe { - return format!("{} {}", msg, self.message_raw); + // TODO(benjaminch): Handle raw / safe as for event message + if self.message_raw != *msg { + return format!("{} {}", msg, self.message_raw); + } } self.message_raw.to_string() @@ -82,7 +86,7 @@ impl CommandError { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] /// Tag: unique identifier for an error. pub enum Tag { /// Unknown: unknown error. @@ -155,6 +159,8 @@ pub enum Tag { K8sNodeIsNotReadyWithTheRequestedVersion, /// K8sNodeIsNotReady: represents an error where the given node is not ready. K8sNodeIsNotReady, + /// K8sValidateRequiredCPUandBurstableError: represents an error validating required CPU and burstable. + K8sValidateRequiredCPUandBurstableError, /// CannotFindRequiredBinary: represents an error where a required binary is not found on the system. CannotFindRequiredBinary, /// SubnetsCountShouldBeEven: represents an error where subnets count should be even to have as many public than private subnets. @@ -173,6 +179,8 @@ pub enum Tag { TerraformErrorWhileExecutingPipeline, /// TerraformErrorWhileExecutingDestroyPipeline: represents an error while executing Terraform destroying pipeline. TerraformErrorWhileExecutingDestroyPipeline, + /// TerraformContextUnsupportedParameterValue: represents an error while trying to render terraform context because of unsupported parameter value. + TerraformContextUnsupportedParameterValue, /// HelmChartsSetupError: represents an error while trying to setup helm charts. HelmChartsSetupError, /// HelmChartsDeployError: represents an error while trying to deploy helm charts. @@ -197,8 +205,44 @@ pub enum Tag { ObjectStorageCannotCreateBucket, /// ObjectStorageCannotPutFileIntoBucket: represents an error while trying to put a file into an object storage bucket. ObjectStorageCannotPutFileIntoBucket, + /// ClientServiceFailedToStart: represent an error while trying to start a client's service. + ClientServiceFailedToStart, + /// ClientServiceFailedToDeployBeforeStart: represents an error while trying to deploy a client's service before start. + ClientServiceFailedToDeployBeforeStart, + /// DatabaseFailedToStartAfterSeveralRetries: represents an error while trying to start a database after several retries. + DatabaseFailedToStartAfterSeveralRetries, + /// RouterFailedToDeploy: represents an error while trying to deploy a router. + RouterFailedToDeploy, + /// CloudProviderClientInvalidCredentials: represents an error where client credentials for a cloud providers appear to be invalid. + CloudProviderClientInvalidCredentials, /// CloudProviderApiMissingInfo: represents an error while expecting mandatory info CloudProviderApiMissingInfo, + /// ServiceInvalidVersionNumberError: represents an error where the version number is not valid. + VersionNumberParsingError, + /// NotImplementedError: represents an error where feature / code has not been implemented yet. + NotImplementedError, + /// TaskCancellationRequested: represents an error where current task cancellation has been requested. + TaskCancellationRequested, + /// BuilderDockerCannotFindAnyDockerfile: represents an error when trying to get a Dockerfile. + BuilderDockerCannotFindAnyDockerfile, + /// BuilderDockerCannotReadDockerfile: represents an error while trying to read Dockerfile. + BuilderDockerCannotReadDockerfile, + /// BuilderDockerCannotExtractEnvVarsFromDockerfile: represents an error while trying to extract ENV vars from Dockerfile. + BuilderDockerCannotExtractEnvVarsFromDockerfile, + /// BuilderDockerCannotBuildContainerImage: represents an error while trying to build Docker container image. + BuilderDockerCannotBuildContainerImage, + /// BuilderBuildpackInvalidLanguageFormat: represents an error where buildback requested language has wrong format. + BuilderBuildpackInvalidLanguageFormat, + /// BuilderBuildpackCannotBuildContainerImage: represents an error while trying to build container image with Buildpack. + BuilderBuildpackCannotBuildContainerImage, + /// BuilderGetBuildError: represents an error when builder is trying to get parent build. + BuilderGetBuildError, + /// BuilderCloningRepositoryError: represents an error when builder is trying to clone a git repository. + BuilderCloningRepositoryError, + /// DockerPushImageError: represents an error when trying to push a docker image. + DockerPushImageError, + /// DockerPullImageError: represents an error when trying to pull a docker image. + DockerPullImageError, } #[derive(Clone, Debug)] @@ -291,6 +335,40 @@ impl EngineError { } } + /// Creates new engine error from legacy engine error easing migration. + pub fn new_from_legacy_engine_error(e: LegacyEngineError) -> Self { + let message = e.message.unwrap_or("".to_string()); + EngineError { + tag: Tag::Unknown, + event_details: EventDetails::new( + None, + QoveryIdentifier::new("".to_string()), + QoveryIdentifier::new("".to_string()), + QoveryIdentifier::new(e.execution_id.to_string()), + None, + Stage::General(GeneralStep::UnderMigration), + match e.scope { + EngineErrorScope::Engine => Transmitter::Kubernetes("".to_string(), "".to_string()), + EngineErrorScope::BuildPlatform(id, name) => Transmitter::BuildPlatform(id, name), + EngineErrorScope::ContainerRegistry(id, name) => Transmitter::ContainerRegistry(id, name), + EngineErrorScope::CloudProvider(id, name) => Transmitter::CloudProvider(id, name), + EngineErrorScope::Kubernetes(id, name) => Transmitter::Kubernetes(id, name), + EngineErrorScope::DnsProvider(id, name) => Transmitter::DnsProvider(id, name), + EngineErrorScope::ObjectStorage(id, name) => Transmitter::ObjectStorage(id, name), + EngineErrorScope::Environment(id, name) => Transmitter::Environment(id, name), + EngineErrorScope::Database(id, db_type, name) => Transmitter::Database(id, db_type, name), + EngineErrorScope::Application(id, name) => Transmitter::Application(id, name), + EngineErrorScope::Router(id, name) => Transmitter::Router(id, name), + }, + ), + qovery_log_message: message.to_string(), + user_log_message: message.to_string(), + message: None, + link: None, + hint_message: None, + } + } + /// Converts to legacy engine error easing migration. pub fn to_legacy_engine_error(self) -> LegacyEngineError { LegacyEngineError::new( @@ -382,8 +460,6 @@ impl EngineError { /// Missing API info from the Cloud provider itself /// - /// - /// /// Arguments: /// /// * `event_details`: Error linked event details. @@ -1228,6 +1304,36 @@ impl EngineError { ) } + /// Creates new error for kubernetes validate required CPU and burstable. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `total_cpus_raw`: Total CPUs raw format. + /// * `cpu_burst_raw`: CPU burst raw. + /// * `raw_error`: Raw error message. + pub fn new_k8s_validate_required_cpu_and_burstable_error( + event_details: EventDetails, + total_cpus_raw: String, + cpu_burst_raw: String, + raw_error: CommandError, + ) -> EngineError { + let message = format!( + "Error while trying to validate required CPU ({}) and burstable ({}).", + total_cpus_raw, cpu_burst_raw + ); + + EngineError::new( + event_details, + Tag::K8sValidateRequiredCPUandBurstableError, + message.to_string(), + message.to_string(), + Some(raw_error), + None, + Some("Please ensure your configuration is valid.".to_string()), + ) + } + /// Creates new error for kubernetes not being able to get crash looping pods. /// /// Arguments: @@ -1444,6 +1550,38 @@ impl EngineError { ) } + /// Creates new error representing an unsupported value for Terraform context parameter. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `service_type`: Service type. + /// * `parameter_name`: Context parameter name. + /// * `parameter_value`: Context parameter value. + /// * `raw_error`: Raw error message. + pub fn new_terraform_unsupported_context_parameter_value( + event_details: EventDetails, + service_type: String, + parameter_name: String, + parameter_value: String, + raw_error: Option, + ) -> EngineError { + let message = format!( + "{} value `{}` not supported for parameter `{}`", + service_type, parameter_value, parameter_name, + ); + + EngineError::new( + event_details, + Tag::TerraformContextUnsupportedParameterValue, + message.to_string(), + message.to_string(), + raw_error, + None, + None, + ) + } + /// Creates new error while setup Helm charts to deploy. /// /// Arguments: @@ -1637,11 +1775,11 @@ impl EngineError { /// /// * `event_details`: Error linked event details. /// * `product_name`: Product name for which version is not supported. - /// * `raw_error`: Raw error message. + /// * `version`: unsupported version raw string. pub fn new_unsupported_version_error( event_details: EventDetails, product_name: String, - version: VersionsNumber, + version: String, ) -> EngineError { let message = format!( "Error, version `{}` is not supported for `{}`.", @@ -1739,6 +1877,158 @@ impl EngineError { ) } + /// Creates new error while trying to start a client service. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `service_id`: Client service ID. + /// * `service_name`: Client service name. + pub fn new_client_service_failed_to_start_error( + event_details: EventDetails, + service_id: String, + service_name: String, + ) -> EngineError { + // TODO(benjaminch): Service should probably passed otherwise, either inside event_details or via a new dedicated struct. + let message = format!("Service `{}` (id `{}`) failed to start. ⤬", service_name, service_id); + + EngineError::new( + event_details, + Tag::ClientServiceFailedToStart, + message.to_string(), + message.to_string(), + None, + None, + Some("Ensure you can run it without issues with `qovery run` and check its logs from the web interface or the CLI with `qovery log`. \ + This issue often occurs due to ports misconfiguration. Make sure you exposed the correct port (using EXPOSE statement in Dockerfile or via Qovery configuration).".to_string()), + ) + } + + /// Creates new error while trying to deploy a client service before start. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `service_id`: Client service ID. + /// * `service_name`: Client service name. + pub fn new_client_service_failed_to_deploy_before_start_error( + event_details: EventDetails, + service_id: String, + service_name: String, + ) -> EngineError { + // TODO(benjaminch): Service should probably passed otherwise, either inside event_details or via a new dedicated struct. + let message = format!( + "Service `{}` (id `{}`) failed to deploy (before start).", + service_name, service_id + ); + + EngineError::new( + event_details, + Tag::ClientServiceFailedToDeployBeforeStart, + message.to_string(), + message.to_string(), + None, + None, + None, + ) + } + + /// Creates new error while trying to start a client service before start. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `service_id`: Client service ID. + /// * `service_type`: Client service type. + /// * `raw_error`: Raw error message. + pub fn new_database_failed_to_start_after_several_retries( + event_details: EventDetails, + service_id: String, + service_type: String, + raw_error: Option, + ) -> EngineError { + let message = format!( + "Database `{}` (id `{}`) failed to start after several retries.", + service_type, service_id + ); + + EngineError::new( + event_details, + Tag::DatabaseFailedToStartAfterSeveralRetries, + message.to_string(), + message.to_string(), + raw_error, + None, + None, + ) + } + + /// Creates new error while trying to deploy a router. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + pub fn new_router_failed_to_deploy(event_details: EventDetails) -> EngineError { + let message = "Router has failed to be deployed."; + + EngineError::new( + event_details, + Tag::RouterFailedToDeploy, + message.to_string(), + message.to_string(), + None, + None, + None, + ) + } + + /// Creates new error when trying to connect to user's account with its credentials. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + pub fn new_client_invalid_cloud_provider_credentials(event_details: EventDetails) -> EngineError { + let message = "Your cloud provider account seems to be no longer valid (bad credentials)."; + + EngineError::new( + event_details, + Tag::CloudProviderClientInvalidCredentials, + message.to_string(), + message.to_string(), + None, + None, + Some("Please contact your Organization administrator to fix or change the Credentials.".to_string()), + ) + } + + /// Creates new error when trying to parse a version number. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `raw_version_number`: Raw version number string. + /// * `raw_error`: Raw error message. + pub fn new_version_number_parsing_error( + event_details: EventDetails, + raw_version_number: String, + raw_error: CommandError, + ) -> EngineError { + let message = format!( + "Error while trying to parse `{}` to a version number.", + raw_version_number + ); + + EngineError::new( + event_details, + Tag::VersionNumberParsingError, + message.to_string(), + message.to_string(), + Some(raw_error), + None, + None, + ) + } + /// Creates new error while trying to get cluster. /// /// Arguments: @@ -1801,4 +2091,303 @@ impl EngineError { Some("Please contact Qovery support for investigation.".to_string()), ) } + + /// Current task cancellation has been requested. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + pub fn new_task_cancellation_requested(event_details: EventDetails) -> EngineError { + let message = "Task cancellation has been requested."; + + EngineError::new( + event_details, + Tag::TaskCancellationRequested, + message.to_string(), + message.to_string(), + None, + None, + None, + ) + } + + /// Creates new error when trying to get Dockerfile. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `location_path`: Dockerfile location path. + pub fn new_docker_cannot_find_dockerfile(event_details: EventDetails, location_path: String) -> EngineError { + let message = format!("Dockerfile not found at location `{}`.", location_path); + + EngineError::new( + event_details, + Tag::BuilderDockerCannotFindAnyDockerfile, + message.to_string(), + message.to_string(), + None, + None, + Some("Your Dockerfile is not present at the specified location, check your settings.".to_string()), + ) + } + + /// Creates new error buildpack invalid language format. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `requested_language`: Requested language. + pub fn new_buildpack_invalid_language_format( + event_details: EventDetails, + requested_language: String, + ) -> EngineError { + let message = format!( + "Cannot build: Invalid buildpacks language format: `{}`.", + requested_language + ); + + EngineError::new( + event_details, + Tag::BuilderBuildpackInvalidLanguageFormat, + message.to_string(), + message.to_string(), + None, + None, + Some("Expected format `builder[@version]`.".to_string()), + ) + } + + /// Creates new error when trying to build container. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `container_image_name`: Container image name. + /// * `raw_error`: Raw error message. + pub fn new_buildpack_cannot_build_container_image( + event_details: EventDetails, + container_image_name: String, + builders: Vec, + raw_error: CommandError, + ) -> EngineError { + let message = "Cannot find a builder to build application."; + + EngineError::new( + event_details, + Tag::BuilderBuildpackCannotBuildContainerImage, + message.to_string(), + message.to_string(), + Some(raw_error), + None, + Some(format!( + "Qovery can't build your container image {} with one of the following builders: {}. Please do provide a valid Dockerfile to build your application or contact the support.", + container_image_name, + builders.join(", ") + ),), + ) + } + + /// Creates new error when trying to get build. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `commit_id`: Commit ID of build to be retrieved. + /// * `raw_error`: Raw error message. + pub fn new_builder_get_build_error( + event_details: EventDetails, + commit_id: String, + raw_error: CommandError, + ) -> EngineError { + let message = format!("Error while trying to get build with commit ID: `{}`.", commit_id); + + EngineError::new( + event_details, + Tag::BuilderGetBuildError, + message.to_string(), + message.to_string(), + Some(raw_error), + None, + None, + ) + } + + /// Creates new error when builder is trying to clone a git repository. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `repository_url`: Repository URL. + /// * `raw_error`: Raw error message. + pub fn new_builder_clone_repository_error( + event_details: EventDetails, + repository_url: String, + raw_error: CommandError, + ) -> EngineError { + let message = format!("Error while cloning repository `{}`.", repository_url); + + EngineError::new( + event_details, + Tag::BuilderCloningRepositoryError, + message.to_string(), + message.to_string(), + Some(raw_error), + None, + None, + ) + } + + /// Creates new error when something went wrong because it's not implemented. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + pub fn new_not_implemented_error(event_details: EventDetails) -> EngineError { + let message = "Error, something went wrong because it's not implemented."; + + EngineError::new( + event_details, + Tag::NotImplementedError, + message.to_string(), + message.to_string(), + None, + None, + None, + ) + } + + /// Creates new error when trying to push a Docker image. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `image_name`: Docker image name. + /// * `repository_url`: Repository URL. + /// * `raw_error`: Raw error message. + pub fn new_docker_push_image_error( + event_details: EventDetails, + image_name: String, + repository_url: String, + raw_error: CommandError, + ) -> EngineError { + let message = format!( + "Error, trying to push Docker image `{}` to repository `{}`.", + image_name, repository_url + ); + + EngineError::new( + event_details, + Tag::DockerPushImageError, + message.to_string(), + message.to_string(), + Some(raw_error), + None, + None, + ) + } + + /// Creates new error when trying to pull a Docker image. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `image_name`: Docker image name. + /// * `repository_url`: Repository URL. + /// * `raw_error`: Raw error message. + pub fn new_docker_pull_image_error( + event_details: EventDetails, + image_name: String, + repository_url: String, + raw_error: CommandError, + ) -> EngineError { + let message = format!( + "Error, trying to pull Docker image `{}` from repository `{}`.", + image_name, repository_url + ); + + EngineError::new( + event_details, + Tag::DockerPullImageError, + message.to_string(), + message.to_string(), + Some(raw_error), + None, + None, + ) + } + + /// Creates new error when trying to read Dockerfile content. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `dockerfile_path`: Dockerfile path. + /// * `raw_error`: Raw error message. + pub fn new_docker_cannot_read_dockerfile( + event_details: EventDetails, + dockerfile_path: String, + raw_error: CommandError, + ) -> EngineError { + let message = format!("Can't read Dockerfile `{}`.", dockerfile_path); + + EngineError::new( + event_details, + Tag::BuilderDockerCannotReadDockerfile, + message.to_string(), + message.to_string(), + Some(raw_error), + None, + None, + ) + } + + /// Creates new error when trying to extract env vars from Dockerfile. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `dockerfile_path`: Dockerfile path. + /// * `raw_error`: Raw error message. + pub fn new_docker_cannot_extract_env_vars_from_dockerfile( + event_details: EventDetails, + dockerfile_path: String, + raw_error: CommandError, + ) -> EngineError { + let message = format!("Can't extract ENV vars from Dockerfile `{}`.", dockerfile_path); + + EngineError::new( + event_details, + Tag::BuilderDockerCannotExtractEnvVarsFromDockerfile, + message.to_string(), + message.to_string(), + Some(raw_error), + None, + None, + ) + } + + /// Creates new error when trying to build Docker container. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `container_image_name`: Container image name. + /// * `raw_error`: Raw error message. + pub fn new_docker_cannot_build_container_image( + event_details: EventDetails, + container_image_name: String, + raw_error: CommandError, + ) -> EngineError { + let message = format!("Error while building container image `{}`.", container_image_name); + + EngineError::new( + event_details, + Tag::BuilderDockerCannotBuildContainerImage, + message.to_string(), + message.to_string(), + Some(raw_error), + None, + Some("It looks like there is something wrong in your Dockerfile. Try building the application locally with `docker build --no-cache`.".to_string()), + ) + } } diff --git a/src/events/io.rs b/src/events/io.rs index 3ecd8815..c9e21af8 100644 --- a/src/events/io.rs +++ b/src/events/io.rs @@ -23,6 +23,7 @@ pub enum EngineEvent { }, Error { error: EngineError, + message: Option, }, #[deprecated(note = "event status is carried by EventDetails directly")] Waiting { @@ -76,8 +77,12 @@ impl From for EngineEvent { details: EventDetails::from(d), message: EventMessage::from(m), }, - events::EngineEvent::Error(e) => EngineEvent::Error { + events::EngineEvent::Error(e, m) => EngineEvent::Error { error: EngineError::from(e), + message: match m { + Some(msg) => Some(EventMessage::from(msg)), + None => None, + }, }, events::EngineEvent::Waiting(d, m) => EngineEvent::Waiting { details: EventDetails::from(d), @@ -151,6 +156,7 @@ pub enum GeneralStep { RetrieveClusterConfig, RetrieveClusterResources, ValidateSystemRequirements, + UnderMigration, } impl From for GeneralStep { @@ -159,6 +165,7 @@ impl From for GeneralStep { events::GeneralStep::RetrieveClusterConfig => GeneralStep::RetrieveClusterConfig, events::GeneralStep::RetrieveClusterResources => GeneralStep::RetrieveClusterResources, events::GeneralStep::ValidateSystemRequirements => GeneralStep::ValidateSystemRequirements, + events::GeneralStep::UnderMigration => GeneralStep::UnderMigration, } } } diff --git a/src/events/mod.rs b/src/events/mod.rs index 444efcc3..659b3d37 100644 --- a/src/events/mod.rs +++ b/src/events/mod.rs @@ -5,7 +5,7 @@ pub mod io; extern crate url; use crate::cloud_provider::Kind; -use crate::errors::EngineError; +use crate::errors::{CommandError, EngineError}; use crate::models::QoveryIdentifier; use std::fmt::{Display, Formatter}; @@ -19,7 +19,7 @@ pub enum EngineEvent { /// Warning: represents a warning message event. Warning(EventDetails, EventMessage), /// Error: represents an error event. - Error(EngineError), + Error(EngineError, Option), /// Waiting: represents an engine waiting event. /// /// Engine is waiting for a task to be done. @@ -52,7 +52,7 @@ impl EngineEvent { EngineEvent::Debug(details, _message) => details, EngineEvent::Info(details, _message) => details, EngineEvent::Warning(details, _message) => details, - EngineEvent::Error(engine_error) => engine_error.event_details(), + EngineEvent::Error(engine_error, _message) => engine_error.event_details(), EngineEvent::Waiting(details, _message) => details, EngineEvent::Deploying(details, _message) => details, EngineEvent::Pausing(details, _message) => details, @@ -69,7 +69,7 @@ impl EngineEvent { EngineEvent::Debug(_details, message) => message.message(message_verbosity), EngineEvent::Info(_details, message) => message.message(message_verbosity), EngineEvent::Warning(_details, message) => message.message(message_verbosity), - EngineEvent::Error(engine_error) => engine_error.message(), + EngineEvent::Error(engine_error, _message) => engine_error.message(), EngineEvent::Waiting(_details, message) => message.message(message_verbosity), EngineEvent::Deploying(_details, message) => message.message(message_verbosity), EngineEvent::Pausing(_details, message) => message.message(message_verbosity), @@ -142,6 +142,12 @@ impl EventMessage { } } +impl From for EventMessage { + fn from(e: CommandError) -> Self { + EventMessage::new(e.message_raw(), e.message_safe()) + } +} + impl Display for EventMessage { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str(self.message(EventMessageVerbosity::SafeOnly).as_str()) // By default, expose only the safe message. @@ -193,6 +199,8 @@ pub enum GeneralStep { RetrieveClusterConfig, /// RetrieveClusterResources: retrieving cluster resources RetrieveClusterResources, + /// UnderMigration: error migration hasn't been completed yet. + UnderMigration, } impl Display for GeneralStep { @@ -204,6 +212,7 @@ impl Display for GeneralStep { GeneralStep::RetrieveClusterConfig => "retrieve-cluster-config", GeneralStep::RetrieveClusterResources => "retrieve-cluster-resources", GeneralStep::ValidateSystemRequirements => "validate-system-requirements", + GeneralStep::UnderMigration => "under-migration", } ) } diff --git a/src/lib.rs b/src/lib.rs index aa850aaf..384d3431 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,11 @@ +#![allow(deprecated)] + extern crate tera; #[macro_use] extern crate tracing; extern crate trust_dns_resolver; pub mod build_platform; -#[allow(deprecated)] pub mod cloud_provider; pub mod cmd; pub mod constants; diff --git a/src/logger.rs b/src/logger.rs index cbe8d573..f5a1a2bb 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -116,25 +116,28 @@ mod tests { let test_cases = vec![ TestCase { log_level: LogLevel::Error, - event: EngineEvent::Error(EngineError::new_unknown( - EventDetails::new( - Some(Kind::Scw), - orga_id.clone(), - cluster_id.clone(), - execution_id.clone(), - Some(ScwRegion::Paris.as_str().to_string()), - Stage::Infrastructure(InfrastructureStep::Create), - Transmitter::Kubernetes(cluster_id.to_string(), cluster_name.to_string()), + event: EngineEvent::Error( + EngineError::new_unknown( + EventDetails::new( + Some(Kind::Scw), + orga_id.clone(), + cluster_id.clone(), + execution_id.clone(), + Some(ScwRegion::Paris.as_str().to_string()), + Stage::Infrastructure(InfrastructureStep::Create), + Transmitter::Kubernetes(cluster_id.to_string(), cluster_name.to_string()), + ), + qovery_message.to_string(), + user_message.to_string(), + Some(errors::CommandError::new( + safe_message.to_string(), + Some(raw_message.to_string()), + )), + Some(link.clone()), + Some(hint.to_string()), ), - qovery_message.to_string(), - user_message.to_string(), - Some(errors::CommandError::new( - safe_message.to_string(), - Some(raw_message.to_string()), - )), - Some(link.clone()), - Some(hint.to_string()), - )), + None, + ), description: "Error event", }, TestCase { diff --git a/src/models.rs b/src/models.rs index b513be27..01106c0f 100644 --- a/src/models.rs +++ b/src/models.rs @@ -23,6 +23,7 @@ use crate::cloud_provider::utilities::VersionsNumber; use crate::cloud_provider::CloudProvider; use crate::cloud_provider::Kind as CPKind; use crate::git; +use crate::logger::Logger; use crate::utilities::get_image_tag; #[derive(Clone, Debug)] @@ -100,13 +101,14 @@ impl Environment { context: &Context, built_applications: &Vec>, cloud_provider: &dyn CloudProvider, + logger: Box, ) -> crate::cloud_provider::environment::Environment { let applications = self .applications .iter() .map(|x| match built_applications.iter().find(|y| x.id.as_str() == y.id()) { - Some(app) => x.to_stateless_service(context, app.image().clone(), cloud_provider), - _ => x.to_stateless_service(context, x.to_image(), cloud_provider), + Some(app) => x.to_stateless_service(context, app.image().clone(), cloud_provider, logger.clone()), + _ => x.to_stateless_service(context, x.to_image(), cloud_provider, logger.clone()), }) .filter(|x| x.is_some()) .map(|x| x.unwrap()) @@ -115,7 +117,7 @@ impl Environment { let routers = self .routers .iter() - .map(|x| x.to_stateless_service(context, cloud_provider)) + .map(|x| x.to_stateless_service(context, cloud_provider, logger.clone())) .filter(|x| x.is_some()) .map(|x| x.unwrap()) .collect::>(); @@ -129,7 +131,7 @@ impl Environment { let databases = self .databases .iter() - .map(|x| x.to_stateful_service(context, cloud_provider)) + .map(|x| x.to_stateful_service(context, cloud_provider, logger.clone())) .filter(|x| x.is_some()) .map(|x| x.unwrap()) .collect::>(); @@ -221,6 +223,7 @@ impl Application { context: &Context, image: &Image, cloud_provider: &dyn CloudProvider, + logger: Box, ) -> Option> { let environment_variables = to_environment_variable(&self.environment_vars); let listeners = cloud_provider.listeners().clone(); @@ -242,6 +245,7 @@ impl Application { self.storage.iter().map(|s| s.to_aws_storage()).collect::>(), environment_variables, listeners, + logger, ))), CPKind::Do => Some(Box::new( crate::cloud_provider::digitalocean::application::Application::new( @@ -260,6 +264,7 @@ impl Application { self.storage.iter().map(|s| s.to_do_storage()).collect::>(), environment_variables, listeners, + logger, ), )), CPKind::Scw => Some(Box::new( @@ -279,6 +284,7 @@ impl Application { self.storage.iter().map(|s| s.to_scw_storage()).collect::>(), environment_variables, listeners, + logger, ), )), } @@ -289,6 +295,7 @@ impl Application { context: &Context, image: Image, cloud_provider: &dyn CloudProvider, + logger: Box, ) -> Option> { let environment_variables = to_environment_variable(&self.environment_vars); let listeners = cloud_provider.listeners().clone(); @@ -310,6 +317,7 @@ impl Application { self.storage.iter().map(|s| s.to_aws_storage()).collect::>(), environment_variables, listeners, + logger.clone(), ))), CPKind::Do => Some(Box::new( crate::cloud_provider::digitalocean::application::Application::new( @@ -328,6 +336,7 @@ impl Application { self.storage.iter().map(|s| s.to_do_storage()).collect::>(), environment_variables, listeners, + logger.clone(), ), )), CPKind::Scw => Some(Box::new( @@ -347,6 +356,7 @@ impl Application { self.storage.iter().map(|s| s.to_scw_storage()).collect::>(), environment_variables, listeners, + logger.clone(), ), )), } @@ -583,6 +593,7 @@ impl Router { &self, context: &Context, cloud_provider: &dyn CloudProvider, + logger: Box, ) -> Option> { let custom_domains = self .custom_domains @@ -616,6 +627,7 @@ impl Router { routes, self.sticky_sessions_enabled, listeners, + logger, )); Some(router) } @@ -631,6 +643,7 @@ impl Router { routes, self.sticky_sessions_enabled, listeners, + logger, )); Some(router) } @@ -645,6 +658,7 @@ impl Router { routes, self.sticky_sessions_enabled, listeners, + logger, )); Some(router) } @@ -701,6 +715,7 @@ impl Database { &self, context: &Context, cloud_provider: &dyn CloudProvider, + logger: Box, ) -> Option> { let database_options = DatabaseOptions { mode: self.mode.clone(), @@ -734,6 +749,7 @@ impl Database { self.database_instance_type.as_str(), database_options, listeners, + logger, )); Some(db) @@ -752,6 +768,7 @@ impl Database { self.database_instance_type.as_str(), database_options, listeners, + logger, )); Some(db) @@ -770,6 +787,7 @@ impl Database { self.database_instance_type.as_str(), database_options, listeners, + logger, )); Some(db) @@ -788,6 +806,7 @@ impl Database { self.database_instance_type.as_str(), database_options, listeners, + logger, )); Some(db) @@ -809,6 +828,7 @@ impl Database { self.database_instance_type.as_str(), database_options, listeners, + logger, ), ); @@ -829,6 +849,7 @@ impl Database { self.database_instance_type.as_str(), database_options, listeners, + logger, )); Some(db) @@ -848,6 +869,7 @@ impl Database { self.database_instance_type.as_str(), database_options, listeners, + logger, )); Some(db) @@ -867,6 +889,7 @@ impl Database { self.database_instance_type.as_str(), database_options, listeners, + logger, )); Some(db) @@ -889,12 +912,16 @@ impl Database { self.database_instance_type.as_str(), database_options, listeners, + logger.clone(), )); Some(db) } Err(e) => { - error!("{}", format!("error while parsing postgres version, error: {}", e)); + error!( + "{}", + format!("error while parsing postgres version, error: {}", e.message()) + ); None } }, @@ -914,12 +941,16 @@ impl Database { self.database_instance_type.as_str(), database_options, listeners, + logger.clone(), )); Some(db) } Err(e) => { - error!("{}", format!("error while parsing mysql version, error: {}", e)); + error!( + "{}", + format!("error while parsing mysql version, error: {}", e.message()) + ); None } }, @@ -938,6 +969,7 @@ impl Database { self.database_instance_type.as_str(), database_options, listeners, + logger.clone(), )); Some(db) @@ -957,6 +989,7 @@ impl Database { self.database_instance_type.as_str(), database_options, listeners, + logger, )); Some(db) diff --git a/src/transaction.rs b/src/transaction.rs index 16831e4b..6b3b41ce 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -6,8 +6,9 @@ use crate::cloud_provider::kubernetes::Kubernetes; use crate::cloud_provider::service::{Application, Service}; use crate::container_registry::PushResult; use crate::engine::Engine; -use crate::error::EngineError; -use crate::errors::EngineError as NewEngineError; +use crate::errors::{EngineError, Tag}; +use crate::events::{EngineEvent, EventMessage}; +use crate::logger::{LogLevel, Logger}; use crate::models::{ Action, Environment, EnvironmentAction, EnvironmentError, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope, @@ -28,7 +29,7 @@ impl<'a> Transaction<'a> { } } - pub fn create_kubernetes(&mut self, kubernetes: &'a dyn Kubernetes) -> Result<(), NewEngineError> { + pub fn create_kubernetes(&mut self, kubernetes: &'a dyn Kubernetes) -> Result<(), EngineError> { match kubernetes.is_valid() { Ok(_) => { self.steps.push(Step::CreateKubernetes(kubernetes)); @@ -38,7 +39,7 @@ impl<'a> Transaction<'a> { } } - pub fn pause_kubernetes(&mut self, kubernetes: &'a dyn Kubernetes) -> Result<(), NewEngineError> { + pub fn pause_kubernetes(&mut self, kubernetes: &'a dyn Kubernetes) -> Result<(), EngineError> { match kubernetes.is_valid() { Ok(_) => { self.steps.push(Step::PauseKubernetes(kubernetes)); @@ -48,7 +49,7 @@ impl<'a> Transaction<'a> { } } - pub fn delete_kubernetes(&mut self, kubernetes: &'a dyn Kubernetes) -> Result<(), NewEngineError> { + pub fn delete_kubernetes(&mut self, kubernetes: &'a dyn Kubernetes) -> Result<(), EngineError> { match kubernetes.is_valid() { Ok(_) => { self.steps.push(Step::DeleteKubernetes(kubernetes)); @@ -151,17 +152,12 @@ impl<'a> Transaction<'a> { container_registry.kind() )) ); - return Err(err); + return Err(EngineError::new_from_legacy_engine_error(err)); } }; } Err(err) => { - warn!( - "load build app {} cache error: {}", - app.name.as_str(), - err.message.clone().unwrap_or("".to_string()) - ); - + warn!("load build app {} cache error: {}", app.name.as_str(), err.message()); return Err(err); } }; @@ -173,6 +169,7 @@ impl<'a> Transaction<'a> { &self, environment: &Environment, option: &DeploymentOption, + logger: Box, ) -> Result>, EngineError> { // do the same for applications let apps_to_build = environment @@ -219,6 +216,7 @@ impl<'a> Transaction<'a> { self.engine.context(), &build_result.build.image, self.engine.cloud_provider(), + logger.clone(), ) { applications.push(app) } @@ -252,7 +250,7 @@ impl<'a> Transaction<'a> { Ok(tuple) => results.push(tuple), Err(err) => { error!("error pushing docker image {:?}", err); - return Err(err); + return Err(EngineError::new_from_legacy_engine_error(err)); } } } @@ -260,10 +258,14 @@ impl<'a> Transaction<'a> { Ok(results) } - fn check_environment(&self, environment: &crate::cloud_provider::environment::Environment) -> TransactionResult { + fn check_environment( + &self, + environment: &crate::cloud_provider::environment::Environment, + logger: Box, + ) -> TransactionResult { if let Err(engine_error) = environment.is_valid() { warn!("ROLLBACK STARTED! an error occurred {:?}", engine_error); - return match self.rollback() { + return match self.rollback(logger) { Ok(_) => TransactionResult::Rollback(engine_error), Err(err) => { error!("ROLLBACK FAILED! fatal error: {:?}", err); @@ -275,25 +277,25 @@ impl<'a> Transaction<'a> { TransactionResult::Ok } - pub fn rollback(&self) -> Result<(), RollbackError> { + pub fn rollback(&self, logger: Box) -> Result<(), RollbackError> { for step in self.executed_steps.iter() { match step { Step::CreateKubernetes(kubernetes) => { // revert kubernetes creation if let Err(err) = kubernetes.on_create_error() { - return Err(RollbackError::CommitError(err.to_legacy_engine_error())); + return Err(RollbackError::CommitError(err)); }; } Step::DeleteKubernetes(kubernetes) => { // revert kubernetes deletion if let Err(err) = kubernetes.on_delete_error() { - return Err(RollbackError::CommitError(err.to_legacy_engine_error())); + return Err(RollbackError::CommitError(err)); }; } Step::PauseKubernetes(kubernetes) => { // revert pause if let Err(err) = kubernetes.on_pause_error() { - return Err(RollbackError::CommitError(err.to_legacy_engine_error())); + return Err(RollbackError::CommitError(err)); }; } Step::BuildEnvironment(_environment_action, _option) => { @@ -301,13 +303,13 @@ impl<'a> Transaction<'a> { } Step::DeployEnvironment(kubernetes, environment_action) => { // revert environment deployment - self.rollback_environment(*kubernetes, *environment_action)?; + self.rollback_environment(*kubernetes, *environment_action, logger.clone())?; } Step::PauseEnvironment(kubernetes, environment_action) => { - self.rollback_environment(*kubernetes, *environment_action)?; + self.rollback_environment(*kubernetes, *environment_action, logger.clone())?; } Step::DeleteEnvironment(kubernetes, environment_action) => { - self.rollback_environment(*kubernetes, *environment_action)?; + self.rollback_environment(*kubernetes, *environment_action, logger.clone())?; } } } @@ -321,21 +323,29 @@ impl<'a> Transaction<'a> { &self, kubernetes: &dyn Kubernetes, environment_action: &EnvironmentAction, + logger: Box, ) -> Result<(), RollbackError> { let qe_environment = |environment: &Environment| { let mut _applications = Vec::with_capacity(environment.applications.len()); for application in environment.applications.iter() { let build = application.to_build(); - if let Some(x) = - application.to_application(self.engine.context(), &build.image, self.engine.cloud_provider()) - { + if let Some(x) = application.to_application( + self.engine.context(), + &build.image, + self.engine.cloud_provider(), + logger.clone(), + ) { _applications.push(x) } } - let qe_environment = - environment.to_qe_environment(self.engine.context(), &_applications, self.engine.cloud_provider()); + let qe_environment = environment.to_qe_environment( + self.engine.context(), + &_applications, + self.engine.cloud_provider(), + logger.clone(), + ); qe_environment }; @@ -354,7 +364,7 @@ impl<'a> Transaction<'a> { let _ = match action { Ok(_) => {} - Err(err) => return Err(RollbackError::CommitError(err.to_legacy_engine_error())), + Err(err) => return Err(RollbackError::CommitError(err)), }; Err(RollbackError::NoFailoverEnvironment) @@ -362,7 +372,7 @@ impl<'a> Transaction<'a> { } } - pub fn commit(mut self) -> TransactionResult { + pub fn commit(mut self, logger: Box) -> TransactionResult { let mut applications_by_environment: HashMap<&Environment, Vec>> = HashMap::new(); for step in self.steps.iter() { @@ -375,7 +385,8 @@ impl<'a> Transaction<'a> { match self.commit_infrastructure( *kubernetes, Action::Create, - kubernetes.on_create().map_err(|e| e.to_legacy_engine_error()), + kubernetes.on_create(), + logger.clone(), ) { TransactionResult::Ok => {} err => { @@ -389,7 +400,8 @@ impl<'a> Transaction<'a> { match self.commit_infrastructure( *kubernetes, Action::Delete, - kubernetes.on_delete().map_err(|e| e.to_legacy_engine_error()), + kubernetes.on_delete(), + logger.clone(), ) { TransactionResult::Ok => {} err => { @@ -400,11 +412,8 @@ impl<'a> Transaction<'a> { } Step::PauseKubernetes(kubernetes) => { // pause kubernetes - match self.commit_infrastructure( - *kubernetes, - Action::Pause, - kubernetes.on_pause().map_err(|e| e.to_legacy_engine_error()), - ) { + match self.commit_infrastructure(*kubernetes, Action::Pause, kubernetes.on_pause(), logger.clone()) + { TransactionResult::Ok => {} err => { error!("Error while pausing infrastructure: {:?}", err); @@ -418,11 +427,21 @@ impl<'a> Transaction<'a> { EnvironmentAction::Environment(te) => te, }; - let applications_builds = match self.build_applications(target_environment, option) { + let applications_builds = match self.build_applications(target_environment, option, logger.clone()) + { Ok(apps) => apps, Err(engine_err) => { - warn!("ROLLBACK STARTED! an error occurred {:?}", engine_err); - return if engine_err.is_cancel() { + logger.log( + LogLevel::Error, + EngineEvent::Error( + engine_err.clone(), + Some(EventMessage::new_from_safe( + "ROLLBACK STARTED! an error occurred".to_string(), + )), + ), + ); + + return if engine_err.tag() == &Tag::TaskCancellationRequested { TransactionResult::Canceled } else { TransactionResult::Rollback(engine_err) @@ -442,7 +461,7 @@ impl<'a> Transaction<'a> { } Err(engine_err) => { warn!("ROLLBACK STARTED! an error occurred {:?}", engine_err); - return match self.rollback() { + return match self.rollback(logger.clone()) { Ok(_) => TransactionResult::Rollback(engine_err), Err(err) => { error!("ROLLBACK FAILED! fatal error: {:?}", err); @@ -460,11 +479,8 @@ impl<'a> Transaction<'a> { *kubernetes, *environment_action, &applications_by_environment, - |qe_env| { - kubernetes - .deploy_environment(qe_env) - .map_err(|e| e.to_legacy_engine_error()) - }, + |qe_env| kubernetes.deploy_environment(qe_env), + logger.clone(), ) { TransactionResult::Ok => {} err => { @@ -479,11 +495,8 @@ impl<'a> Transaction<'a> { *kubernetes, *environment_action, &applications_by_environment, - |qe_env| { - kubernetes - .pause_environment(qe_env) - .map_err(|e| e.to_legacy_engine_error()) - }, + |qe_env| kubernetes.pause_environment(qe_env), + logger.clone(), ) { TransactionResult::Ok => {} err => { @@ -498,11 +511,8 @@ impl<'a> Transaction<'a> { *kubernetes, *environment_action, &applications_by_environment, - |qe_env| { - kubernetes - .delete_environment(qe_env) - .map_err(|e| e.to_legacy_engine_error()) - }, + |qe_env| kubernetes.delete_environment(qe_env), + logger.clone(), ) { TransactionResult::Ok => {} err => { @@ -522,6 +532,7 @@ impl<'a> Transaction<'a> { kubernetes: &dyn Kubernetes, action: Action, result: Result<(), EngineError>, + logger: Box, ) -> TransactionResult { // send back the right progress status fn send_progress(lh: &ListenersHelper, action: Action, execution_id: &str, is_error: bool) { @@ -563,7 +574,7 @@ impl<'a> Transaction<'a> { match result { Err(err) => { warn!("infrastructure ROLLBACK STARTED! an error occurred {:?}", err); - match self.rollback() { + match self.rollback(logger) { Ok(_) => { // an error occurred on infrastructure deployment BUT rolledback is OK send_progress(&lh, action, execution_id, true); @@ -591,6 +602,7 @@ impl<'a> Transaction<'a> { environment_action: &EnvironmentAction, applications_by_environment: &HashMap<&Environment, Vec>>, action_fn: F, + logger: Box, ) -> TransactionResult where F: Fn(&crate::cloud_provider::environment::Environment) -> Result<(), EngineError>, @@ -609,9 +621,10 @@ impl<'a> Transaction<'a> { self.engine.context(), built_applications, kubernetes.cloud_provider(), + logger.clone(), ); - let _ = match self.check_environment(&qe_environment) { + let _ = match self.check_environment(&qe_environment, logger.clone()) { TransactionResult::Ok => {} err => return err, // which it means that an error occurred }; @@ -661,7 +674,7 @@ impl<'a> Transaction<'a> { let _ = match action_fn(&qe_environment) { Err(err) => { - let rollback_result = match self.rollback() { + let rollback_result = match self.rollback(logger) { Ok(_) => TransactionResult::Rollback(err), Err(rollback_err) => { error!("ROLLBACK FAILED! fatal error: {:?}", rollback_err); diff --git a/test_utilities/src/aws.rs b/test_utilities/src/aws.rs index 10feaaef..b9ffabe1 100644 --- a/test_utilities/src/aws.rs +++ b/test_utilities/src/aws.rs @@ -66,7 +66,7 @@ impl Cluster for AWS { let container_registry = Box::new(container_registry_ecr(context)); // use LocalDocker - let build_platform = Box::new(build_platform_local_docker(context)); + let build_platform = Box::new(build_platform_local_docker(context, logger.clone())); // use AWS let cloud_provider = AWS::cloud_provider(context); diff --git a/test_utilities/src/common.rs b/test_utilities/src/common.rs index 258e4630..5a142b15 100644 --- a/test_utilities/src/common.rs +++ b/test_utilities/src/common.rs @@ -120,7 +120,7 @@ impl Infrastructure for Environment { }, ); - tx.commit() + tx.commit(logger.clone()) } fn pause_environment( @@ -151,7 +151,7 @@ impl Infrastructure for Environment { let _ = tx.pause_environment(k.as_ref(), &environment_action); - tx.commit() + tx.commit(logger.clone()) } fn delete_environment( @@ -182,7 +182,7 @@ impl Infrastructure for Environment { let _ = tx.delete_environment(k.as_ref(), &environment_action); - tx.commit() + tx.commit(logger.clone()) } } @@ -990,6 +990,7 @@ pub fn test_db( let context_for_delete = context.clone_not_same_execution_id(); let app_id = generate_id(); + let database_username = "superuser".to_string(); let database_password = generate_password(true); let db_kind_str = db_kind.name().to_string(); @@ -1384,7 +1385,7 @@ pub fn cluster_test( if let Err(err) = deploy_tx.create_kubernetes(kubernetes.as_ref()) { panic!("{:?}", err) } - assert!(matches!(deploy_tx.commit(), TransactionResult::Ok)); + assert!(matches!(deploy_tx.commit(logger.clone()), TransactionResult::Ok)); // Deploy env if any if let Some(env) = environment_to_deploy { @@ -1395,7 +1396,7 @@ pub fn cluster_test( panic!("{:?}", err) } - assert!(matches!(deploy_env_tx.commit(), TransactionResult::Ok)); + assert!(matches!(deploy_env_tx.commit(logger.clone()), TransactionResult::Ok)); } if let Err(err) = metrics_server_test( @@ -1417,14 +1418,14 @@ pub fn cluster_test( if let Err(err) = pause_tx.pause_kubernetes(kubernetes.as_ref()) { panic!("{:?}", err) } - assert!(matches!(pause_tx.commit(), TransactionResult::Ok)); + assert!(matches!(pause_tx.commit(logger.clone()), TransactionResult::Ok)); // Resume if let Err(err) = resume_tx.create_kubernetes(kubernetes.as_ref()) { panic!("{:?}", err) } - assert!(matches!(resume_tx.commit(), TransactionResult::Ok)); + assert!(matches!(resume_tx.commit(logger.clone()), TransactionResult::Ok)); if let Err(err) = metrics_server_test( kubernetes @@ -1458,7 +1459,7 @@ pub fn cluster_test( if let Err(err) = upgrade_tx.create_kubernetes(upgraded_kubernetes.as_ref()) { panic!("{:?}", err) } - assert!(matches!(upgrade_tx.commit(), TransactionResult::Ok)); + assert!(matches!(upgrade_tx.commit(logger.clone()), TransactionResult::Ok)); if let Err(err) = metrics_server_test( upgraded_kubernetes @@ -1477,7 +1478,7 @@ pub fn cluster_test( if let Err(err) = delete_tx.delete_kubernetes(upgraded_kubernetes.as_ref()) { panic!("{:?}", err) } - assert!(matches!(delete_tx.commit(), TransactionResult::Ok)); + assert!(matches!(delete_tx.commit(logger.clone()), TransactionResult::Ok)); return test_name.to_string(); } @@ -1491,14 +1492,14 @@ pub fn cluster_test( if let Err(err) = destroy_env_tx.delete_environment(kubernetes.as_ref(), env) { panic!("{:?}", err) } - assert!(matches!(destroy_env_tx.commit(), TransactionResult::Ok)); + assert!(matches!(destroy_env_tx.commit(logger.clone()), TransactionResult::Ok)); } // Delete if let Err(err) = delete_tx.delete_kubernetes(kubernetes.as_ref()) { panic!("{:?}", err) } - assert!(matches!(delete_tx.commit(), TransactionResult::Ok)); + assert!(matches!(delete_tx.commit(logger.clone()), TransactionResult::Ok)); test_name.to_string() } diff --git a/test_utilities/src/digitalocean.rs b/test_utilities/src/digitalocean.rs index 5f8cce0a..fd17b805 100644 --- a/test_utilities/src/digitalocean.rs +++ b/test_utilities/src/digitalocean.rs @@ -43,7 +43,7 @@ impl Cluster for DO { // use DigitalOcean Container Registry let container_registry = Box::new(container_registry_digital_ocean(context)); // use LocalDocker - let build_platform = Box::new(build_platform_local_docker(context)); + let build_platform = Box::new(build_platform_local_docker(context, logger.clone())); // use Digital Ocean let cloud_provider = DO::cloud_provider(context); diff --git a/test_utilities/src/lib.rs b/test_utilities/src/lib.rs index b06605f9..4092f39e 100644 --- a/test_utilities/src/lib.rs +++ b/test_utilities/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(deprecated)] + #[macro_use] extern crate maplit; diff --git a/test_utilities/src/scaleway.rs b/test_utilities/src/scaleway.rs index e96cc52e..4d32e094 100644 --- a/test_utilities/src/scaleway.rs +++ b/test_utilities/src/scaleway.rs @@ -63,7 +63,7 @@ impl Cluster for Scaleway { let container_registry = Box::new(container_registry_scw(context)); // use LocalDocker - let build_platform = Box::new(build_platform_local_docker(context)); + let build_platform = Box::new(build_platform_local_docker(context, logger.clone())); // use Scaleway let cloud_provider = Scaleway::cloud_provider(context); diff --git a/test_utilities/src/utilities.rs b/test_utilities/src/utilities.rs index 902adf47..d22597d7 100644 --- a/test_utilities/src/utilities.rs +++ b/test_utilities/src/utilities.rs @@ -362,8 +362,8 @@ impl FuncTestsSecrets { } } -pub fn build_platform_local_docker(context: &Context) -> LocalDocker { - LocalDocker::new(context.clone(), "oxqlm3r99vwcmvuj", "qovery-local-docker") +pub fn build_platform_local_docker(context: &Context, logger: Box) -> LocalDocker { + LocalDocker::new(context.clone(), "oxqlm3r99vwcmvuj", "qovery-local-docker", logger) } pub fn init() -> Instant { diff --git a/tests/aws/aws_environment.rs b/tests/aws/aws_environment.rs index 5b45c545..1de17677 100644 --- a/tests/aws/aws_environment.rs +++ b/tests/aws/aws_environment.rs @@ -105,7 +105,7 @@ fn test_build_cache() { ); let ecr = container_registry_ecr(&context); - let local_docker = build_platform_local_docker(&context); + let local_docker = build_platform_local_docker(&context, logger()); let app = environment.applications.first().unwrap(); let image = app.to_image(); diff --git a/tests/digitalocean/do_environment.rs b/tests/digitalocean/do_environment.rs index 7b47e957..d2b04f5d 100644 --- a/tests/digitalocean/do_environment.rs +++ b/tests/digitalocean/do_environment.rs @@ -108,7 +108,7 @@ fn test_build_cache() { ); let docr = container_registry_digital_ocean(&context); - let local_docker = build_platform_local_docker(&context); + let local_docker = build_platform_local_docker(&context, logger()); let app = environment.applications.first().unwrap(); let image = app.to_image(); diff --git a/tests/digitalocean/do_utility_kubernetes_doks_test_cluster.rs b/tests/digitalocean/do_utility_kubernetes_doks_test_cluster.rs index 948f8859..2d11804f 100644 --- a/tests/digitalocean/do_utility_kubernetes_doks_test_cluster.rs +++ b/tests/digitalocean/do_utility_kubernetes_doks_test_cluster.rs @@ -66,7 +66,7 @@ fn create_digitalocean_kubernetes_doks_test_cluster() { if let Err(err) = tx.create_kubernetes(&kubernetes) { panic!("{:?}", err) } - let ret = tx.commit(); + let ret = tx.commit(logger.clone()); assert!(matches!(ret, TransactionResult::Ok)); test_name.to_string() @@ -129,7 +129,7 @@ fn destroy_digitalocean_kubernetes_doks_test_cluster() { if let Err(err) = tx.delete_kubernetes(&kubernetes) { panic!("{:?}", err) } - let ret = tx.commit(); + let ret = tx.commit(logger.clone()); assert!(matches!(ret, TransactionResult::Ok)); test_name.to_string() diff --git a/tests/scaleway/scw_environment.rs b/tests/scaleway/scw_environment.rs index c6186216..870c70f4 100644 --- a/tests/scaleway/scw_environment.rs +++ b/tests/scaleway/scw_environment.rs @@ -112,7 +112,7 @@ fn test_build_cache() { ); let scr = container_registry_scw(&context); - let local_docker = build_platform_local_docker(&context); + let local_docker = build_platform_local_docker(&context, logger()); let app = environment.applications.first().unwrap(); let image = app.to_image(); diff --git a/tests/scaleway/scw_utility_kubernetes_kapsule_test_cluster.rs b/tests/scaleway/scw_utility_kubernetes_kapsule_test_cluster.rs index e68781fa..fa010425 100644 --- a/tests/scaleway/scw_utility_kubernetes_kapsule_test_cluster.rs +++ b/tests/scaleway/scw_utility_kubernetes_kapsule_test_cluster.rs @@ -66,7 +66,7 @@ fn create_scaleway_kubernetes_kapsule_test_cluster() { panic!("{:?}", err) } - assert!(matches!(tx.commit(), TransactionResult::Ok)); + assert!(matches!(tx.commit(logger.clone()), TransactionResult::Ok)); test_name.to_string() }); @@ -127,7 +127,7 @@ fn destroy_scaleway_kubernetes_kapsule_test_cluster() { if let Err(err) = tx.delete_kubernetes(&kubernetes) { panic!("{:?}", err) } - let ret = tx.commit(); + let ret = tx.commit(logger.clone()); assert!(matches!(ret, TransactionResult::Ok)); test_name.to_string()