From c7ffe23ea4c412fbaca9c89c486eae69767bf0f1 Mon Sep 17 00:00:00 2001 From: Benjamin Chastanier Date: Fri, 18 Feb 2022 23:00:38 +0100 Subject: [PATCH] wip --- src/build_platform/local_docker.rs | 268 ++++++--- src/build_platform/mod.rs | 33 +- src/cloud_provider/aws/application.rs | 6 +- src/cloud_provider/aws/databases/mongodb.rs | 8 +- src/cloud_provider/aws/databases/mysql.rs | 30 +- .../aws/databases/postgresql.rs | 10 +- src/cloud_provider/aws/databases/utilities.rs | 1 - src/cloud_provider/aws/mod.rs | 28 +- src/cloud_provider/aws/router.rs | 14 +- .../digitalocean/application.rs | 81 ++- .../digitalocean/databases/mongodb.rs | 80 ++- .../digitalocean/databases/mysql.rs | 76 +-- .../digitalocean/databases/postgresql.rs | 76 ++- .../digitalocean/databases/redis.rs | 76 ++- src/cloud_provider/digitalocean/mod.rs | 27 +- .../digitalocean/network/load_balancer.rs | 30 +- src/cloud_provider/digitalocean/router.rs | 147 +++-- src/cloud_provider/mod.rs | 15 +- src/cloud_provider/scaleway/application.rs | 86 ++- .../scaleway/databases/mongodb.rs | 63 +- .../scaleway/databases/mysql.rs | 97 ++-- .../scaleway/databases/postgresql.rs | 97 ++-- .../scaleway/databases/redis.rs | 76 ++- src/cloud_provider/scaleway/mod.rs | 16 +- src/cloud_provider/scaleway/router.rs | 89 ++- src/cloud_provider/service.rs | 2 +- src/cloud_provider/utilities.rs | 2 - src/container_registry/docker.rs | 193 ++++-- src/container_registry/docker_hub.rs | 115 ++-- src/container_registry/docr.rs | 353 +++++++---- src/container_registry/ecr.rs | 294 ++++++---- src/container_registry/mod.rs | 24 +- .../scaleway_container_registry.rs | 253 +++++--- src/dns_provider/cloudflare.rs | 2 +- src/dns_provider/mod.rs | 13 +- src/engine.rs | 2 +- src/error.rs | 1 + src/errors/mod.rs | 548 ++++++++++++++++++ src/events/mod.rs | 11 +- src/object_storage/mod.rs | 13 +- src/object_storage/s3.rs | 2 +- src/object_storage/scaleway_object_storage.rs | 2 +- src/object_storage/spaces.rs | 2 +- src/transaction.rs | 9 +- 44 files changed, 2354 insertions(+), 1017 deletions(-) diff --git a/src/build_platform/local_docker.rs b/src/build_platform/local_docker.rs index 6c1aad5f..6e40088d 100644 --- a/src/build_platform/local_docker.rs +++ b/src/build_platform/local_docker.rs @@ -7,9 +7,11 @@ use sysinfo::{Disk, DiskExt, SystemExt}; use crate::build_platform::{docker, Build, BuildPlatform, BuildResult, CacheResult, Credentials, Image, Kind}; use crate::cmd::utilities::QoveryCommand; -use crate::error::{EngineError, EngineErrorCause, EngineErrorScope, SimpleError, SimpleErrorKind}; +use crate::errors::{CommandError, EngineError}; +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, }; @@ -25,20 +27,22 @@ const BUILDPACKS_BUILDERS: [&str; 1] = [ ]; /// use Docker in local -pub struct LocalDocker { +pub struct LocalDocker<'a> { context: Context, id: String, name: String, listeners: Listeners, + logger: &'a dyn Logger, } -impl LocalDocker { - pub fn new(context: Context, id: &str, name: &str) -> Self { +impl<'a> LocalDocker<'a> { + pub fn new(context: Context, id: &str, name: &str, logger: &'a dyn Logger) -> 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) } } } @@ -101,9 +110,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)); + Err(engine_error) } }; @@ -129,7 +143,10 @@ impl LocalDocker { let exit_status = cmd.exec_with_timeout( 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 { @@ -141,7 +158,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 { @@ -156,15 +176,10 @@ impl LocalDocker { match exit_status { Ok(_) => Ok(BuildResult { build }), - 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), )), } } @@ -181,8 +196,8 @@ impl LocalDocker { let args = self.context.docker_build_options(); - let mut exit_status: Result<(), SimpleError> = - Err(SimpleError::new(SimpleErrorKind::Other, Some("no builder names"))); + 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 { @@ -245,12 +260,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); } } @@ -272,7 +288,10 @@ impl LocalDocker { .exec_with_timeout( 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 { @@ -284,7 +303,13 @@ 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 { @@ -296,7 +321,7 @@ impl LocalDocker { )); }, ) - .map_err(|err| SimpleError::new(SimpleErrorKind::Other, Some(format!("{:?}", err)))); + .map_err(|err| CommandError::new(format!("{:?}", err), None)); if exit_status.is_ok() { // quit now if the builder successfully build the app @@ -307,19 +332,17 @@ impl LocalDocker { match exit_status { Ok(_) => Ok(BuildResult { build }), 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) } } } @@ -330,7 +353,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), + ) + }) } } @@ -353,11 +381,17 @@ impl BuildPlatform for LocalDocker { fn is_valid(&self) -> Result<(), EngineError> { if !crate::cmd::utilities::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::utilities::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(()) @@ -369,9 +403,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, @@ -449,8 +483,17 @@ impl BuildPlatform for LocalDocker { "Error while cloning repository {}. Error: {:?}", &build.git_repository.url, clone_error ); - error!("{}", message); - return Err(self.engine_error(EngineErrorCause::Internal, message)); + + 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), + ); + + self.logger + .log(LogLevel::Error, EngineEvent::Error(error.clone(), None)); + + return Err(error); } let mut disable_build_cache = false; @@ -481,9 +524,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; }; @@ -508,7 +562,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(), @@ -521,14 +574,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( @@ -561,7 +613,14 @@ impl BuildPlatform for LocalDocker { } 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); @@ -578,7 +637,11 @@ impl BuildPlatform for LocalDocker { )); // FIXME - Err(self.engine_error(EngineErrorCause::Internal, message)) + Err(EngineError::new_not_implemented_error(event_details)) + } + + fn logger(&self) -> &dyn Logger { + self.logger } } @@ -592,38 +655,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"], @@ -631,20 +710,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 2b369928..397d36d9 100644 --- a/src/build_platform/mod.rs +++ b/src/build_platform/mod.rs @@ -1,9 +1,11 @@ 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 std::fmt::{Display, Formatter, Result as FmtResult}; @@ -12,7 +14,7 @@ use std::path::Path; pub mod docker; pub mod local_docker; -pub trait BuildPlatform: Listen { +pub trait BuildPlatform: ToTransmitter { fn context(&self) -> &Context; fn kind(&self) -> Kind; fn id(&self) -> &str; @@ -24,15 +26,17 @@ pub trait BuildPlatform: Listen { fn has_cache(&self, build: &Build) -> Result; fn build(&self, build: Build, force_build: bool) -> Result; fn build_error(&self, build: Build) -> Result; - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::BuildPlatform(self.id().to_string(), self.name().to_string()) - } - fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError { - EngineError::new( - cause, - self.engine_error_scope(), - self.context().execution_id(), - Some(message), + fn logger(&self) -> &dyn Logger; + 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(), ) } } @@ -44,7 +48,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, { @@ -59,7 +63,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 7bcce4f4..d6544517 100644 --- a/src/cloud_provider/aws/application.rs +++ b/src/cloud_provider/aws/application.rs @@ -91,7 +91,7 @@ impl<'a> Application<'a> { } } -impl<'a> crate::cloud_provider::service::Application<'a> for Application<'a> { +impl<'a> crate::cloud_provider::service::Application for Application<'a> { fn image(&self) -> &Image { &self.image } @@ -123,7 +123,7 @@ impl<'a> Helm for Application<'a> { } } -impl<'a> StatelessService<'a> for Application<'a> {} +impl<'a> StatelessService for Application<'a> {} impl<'a> ToTransmitter for Application<'a> { fn to_transmitter(&self) -> Transmitter { @@ -303,7 +303,7 @@ impl<'a> Service for Application<'a> { } fn logger(&self) -> &dyn Logger { - todo!() + self.logger } fn selector(&self) -> Option { diff --git a/src/cloud_provider/aws/databases/mongodb.rs b/src/cloud_provider/aws/databases/mongodb.rs index 3103322a..863d8041 100644 --- a/src/cloud_provider/aws/databases/mongodb.rs +++ b/src/cloud_provider/aws/databases/mongodb.rs @@ -185,7 +185,9 @@ impl<'a> Service for MongoDB<'a> { context.insert("namespace", environment.namespace()); - let version = self.matching_correct_version(self.is_managed_service(), event_details.clone())?; + 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() { @@ -367,12 +369,12 @@ impl<'a> Delete for MongoDB<'a> { self.struct_name(), function_name!(), self.name(), - event_details, + 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(), self.logger) + delete_stateful_service(target, self, event_details, self.logger()) }) } diff --git a/src/cloud_provider/aws/databases/mysql.rs b/src/cloud_provider/aws/databases/mysql.rs index 32a65e79..eb0e2f52 100644 --- a/src/cloud_provider/aws/databases/mysql.rs +++ b/src/cloud_provider/aws/databases/mysql.rs @@ -92,13 +92,13 @@ impl<'a> MySQL<'a> { } } -impl StatefulService for MySQL { +impl<'a> StatefulService for MySQL<'a> { fn is_managed_service(&self) -> bool { self.options.mode == MANAGED } } -impl ToTransmitter for MySQL { +impl<'a> ToTransmitter for MySQL<'a> { fn to_transmitter(&self) -> Transmitter { Transmitter::Database( self.id().to_string(), @@ -108,7 +108,7 @@ impl ToTransmitter for MySQL { } } -impl Service for MySQL { +impl<'a> Service for MySQL<'a> { fn context(&self) -> &Context { &self.context } @@ -190,7 +190,9 @@ impl Service for MySQL { context.insert("namespace", environment.namespace()); - let version = &self.matching_correct_version(self.is_managed_service(), event_details.clone())?; + let version = &self + .matching_correct_version(self.is_managed_service(), event_details.clone())? + .matched_version(); context.insert("version", &version); if self.is_managed_service() { @@ -261,9 +263,9 @@ impl Service for MySQL { } } -impl Database for MySQL {} +impl<'a> Database for MySQL<'a> {} -impl Helm for MySQL { +impl<'a> Helm for MySQL<'a> { fn helm_selector(&self) -> Option { self.selector() } @@ -285,7 +287,7 @@ impl Helm for MySQL { } } -impl Terraform for MySQL { +impl<'a> Terraform for MySQL<'a> { fn terraform_common_resource_dir_path(&self) -> String { format!("{}/aws/services/common", self.context.lib_root_dir()) } @@ -295,7 +297,7 @@ impl Terraform for MySQL { } } -impl Create for MySQL { +impl<'a> Create for MySQL<'a> { #[named] fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); @@ -304,12 +306,12 @@ impl Create for MySQL { self.struct_name(), function_name!(), self.name(), - event_details, + 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(), self.logger) + deploy_stateful_service(target, self, event_details, self.logger()) }) } @@ -333,7 +335,7 @@ impl Create for MySQL { } } -impl Pause for MySQL { +impl<'a> Pause for MySQL<'a> { #[named] fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); @@ -370,7 +372,7 @@ impl Pause for MySQL { } } -impl Delete for MySQL { +impl<'a> Delete for MySQL<'a> { #[named] fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); @@ -384,7 +386,7 @@ impl Delete for MySQL { ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || { - delete_stateful_service(target, self, event_details, self.logger) + delete_stateful_service(target, self, event_details, self.logger()) }) } @@ -407,7 +409,7 @@ impl Delete for MySQL { } } -impl Listen for MySQL { +impl<'a> Listen for MySQL<'a> { fn listeners(&self) -> &Listeners { &self.listeners } diff --git a/src/cloud_provider/aws/databases/postgresql.rs b/src/cloud_provider/aws/databases/postgresql.rs index addd1526..260908a1 100644 --- a/src/cloud_provider/aws/databases/postgresql.rs +++ b/src/cloud_provider/aws/databases/postgresql.rs @@ -190,7 +190,9 @@ impl<'a> Service for PostgreSQL<'a> { context.insert("namespace", environment.namespace()); - let version = self.matching_correct_version(self.is_managed_service(), event_details.clone())?; + 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() { @@ -294,7 +296,7 @@ impl<'a> Create for PostgreSQL<'a> { ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { - deploy_stateful_service(target, self, event_details, self.logger) + deploy_stateful_service(target, self, event_details, self.logger()) }) } @@ -370,7 +372,7 @@ impl<'a> Delete for PostgreSQL<'a> { ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || { - delete_stateful_service(target, self, event_details, self.logger) + delete_stateful_service(target, self, event_details, self.logger()) }) } @@ -386,7 +388,7 @@ impl<'a> Delete for PostgreSQL<'a> { self.struct_name(), function_name!(), self.name(), - event_details.clone(), + event_details, self.logger(), ); diff --git a/src/cloud_provider/aws/databases/utilities.rs b/src/cloud_provider/aws/databases/utilities.rs index eb5e5bc6..ce9748c9 100644 --- a/src/cloud_provider/aws/databases/utilities.rs +++ b/src/cloud_provider/aws/databases/utilities.rs @@ -1,5 +1,4 @@ use crate::cloud_provider::utilities::VersionsNumber; -use crate::error::StringError; use crate::errors::CommandError; use crate::models::DatabaseKind; use std::str::FromStr; diff --git a/src/cloud_provider/aws/mod.rs b/src/cloud_provider/aws/mod.rs index fdd18815..a28f5d9d 100644 --- a/src/cloud_provider/aws/mod.rs +++ b/src/cloud_provider/aws/mod.rs @@ -5,9 +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::models::{Context, Listen, Listener, Listeners}; +use crate::errors::EngineError; +use crate::events::{EventDetails, GeneralStep, Stage}; +use crate::models::{Context, Listen, Listener, Listeners, QoveryIdentifier}; use crate::runtime::block_on; pub mod application; @@ -107,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, )); } } @@ -149,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 { diff --git a/src/cloud_provider/aws/router.rs b/src/cloud_provider/aws/router.rs index 90d681e9..ba30d476 100644 --- a/src/cloud_provider/aws/router.rs +++ b/src/cloud_provider/aws/router.rs @@ -192,7 +192,15 @@ impl<'a> Service for Router<'a> { 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("unable to get external_ingress_hostname_default - what's wrong? This must never happened".to_string()))); + 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(), + ), + ), + ); } }, _ => { @@ -202,9 +210,7 @@ impl<'a> Service for Router<'a> { LogLevel::Warning, EngineEvent::Warning( event_details.clone(), - EventMessage::new_from_safe( - "can't fetch kubernetes config file - what's wrong? This must never happened".to_string(), - ), + EventMessage::new_from_safe("Can't fetch external ingress hostname.".to_string()), ), ); } diff --git a/src/cloud_provider/digitalocean/application.rs b/src/cloud_provider/digitalocean/application.rs index 443a9735..b3f07854 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, }; @@ -14,13 +15,14 @@ use crate::cloud_provider::DeploymentTarget; use crate::cmd::helm::Timeout; use crate::cmd::kubectl::ScalingKind::{Deployment, Statefulset}; use crate::errors::{CommandError, EngineError}; -use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter}; +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; use std::str::FromStr; -pub struct Application { +pub struct Application<'a> { context: Context, id: String, action: Action, @@ -36,9 +38,10 @@ pub struct Application { storage: Vec>, environment_variables: Vec, listeners: Listeners, + logger: &'a dyn Logger, } -impl Application { +impl<'a> Application<'a> { pub fn new( context: Context, id: &str, @@ -55,6 +58,7 @@ impl Application { storage: Vec>, environment_variables: Vec, listeners: Listeners, + logger: &dyn Logger, ) -> Self { Application { context, @@ -72,6 +76,7 @@ impl Application { storage, environment_variables, listeners, + logger, } } @@ -88,7 +93,7 @@ impl Application { } } -impl crate::cloud_provider::service::Application for Application { +impl<'a> crate::cloud_provider::service::Application for Application<'a> { fn image(&self) -> &Image { &self.image } @@ -98,7 +103,7 @@ impl crate::cloud_provider::service::Application for Application { } } -impl Helm for Application { +impl<'a> Helm for Application<'a> { fn helm_selector(&self) -> Option { self.selector() } @@ -120,15 +125,15 @@ impl Helm for Application { } } -impl StatelessService for Application {} +impl<'a> StatelessService for Application<'a> {} -impl ToTransmitter for Application { +impl<'a> ToTransmitter for Application<'a> { fn to_transmitter(&self) -> Transmitter { Transmitter::Application(self.id().to_string(), self.name().to_string()) } } -impl Service for Application { +impl<'a> Service for Application<'a> { fn context(&self) -> &Context { &self.context } @@ -193,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); @@ -204,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()); } } @@ -218,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, )); } }; @@ -286,12 +302,16 @@ impl Service for Application { Ok(context) } + fn logger(&self) -> &dyn Logger { + self.logger + } + fn selector(&self) -> Option { Some(format!("appId={}", self.id)) } } -impl Create for Application { +impl<'a> Create for Application<'a> { #[named] fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); @@ -300,10 +320,12 @@ impl Create 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::Create, || { - deploy_user_stateless_service(target, self, event_details.clone(), self.logger()) + deploy_user_stateless_service(target, self, event_details) }) } @@ -319,15 +341,17 @@ impl Create for Application { self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || { - deploy_stateless_service_error(target, self, event_details.clone(), self.logger()) + deploy_stateless_service_error(target, self) }) } } -impl Pause for Application { +impl<'a> Pause for Application<'a> { #[named] fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause)); @@ -336,6 +360,8 @@ impl Pause for Application { self.struct_name(), function_name!(), self.name(), + event_details, + self.logger(), ); send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || { @@ -354,18 +380,21 @@ 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(()) } } -impl Delete for Application { +impl<'a> Delete for Application<'a> { #[named] fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); @@ -374,10 +403,12 @@ 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, || { - delete_stateless_service(target, self, false, event_details.clone()) + delete_stateless_service(target, self, false, event_details) }) } @@ -393,15 +424,17 @@ 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, || { - delete_stateless_service(target, self, true, event_details.clone()) + delete_stateless_service(target, self, true, event_details) }) } } -impl Listen for Application { +impl<'a> Listen for Application<'a> { fn listeners(&self) -> &Listeners { &self.listeners } diff --git a/src/cloud_provider/digitalocean/databases/mongodb.rs b/src/cloud_provider/digitalocean/databases/mongodb.rs index 730d9e4d..3c3d51d8 100644 --- a/src/cloud_provider/digitalocean/databases/mongodb.rs +++ b/src/cloud_provider/digitalocean/databases/mongodb.rs @@ -3,19 +3,20 @@ 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; -pub struct MongoDB { +pub struct MongoDB<'a> { context: Context, id: String, action: Action, @@ -28,9 +29,10 @@ pub struct MongoDB { database_instance_type: String, options: DatabaseOptions, listeners: Listeners, + logger: &'a dyn Logger, } -impl MongoDB { +impl<'a> MongoDB<'a> { pub fn new( context: Context, id: &str, @@ -44,6 +46,7 @@ impl MongoDB { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: &'a dyn Logger, ) -> Self { MongoDB { context, @@ -58,11 +61,16 @@ 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, + ) } fn cloud_provider_name(&self) -> &str { @@ -74,13 +82,13 @@ impl MongoDB { } } -impl StatefulService for MongoDB { +impl<'a> StatefulService for MongoDB<'a> { fn is_managed_service(&self) -> bool { self.options.mode == MANAGED } } -impl ToTransmitter for MongoDB { +impl<'a> ToTransmitter for MongoDB<'a> { fn to_transmitter(&self) -> Transmitter { Transmitter::Database( self.id().to_string(), @@ -90,7 +98,7 @@ impl ToTransmitter for MongoDB { } } -impl Service for MongoDB { +impl<'a> Service for MongoDB<'a> { fn context(&self) -> &Context { &self.context } @@ -152,17 +160,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 +177,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,18 +222,14 @@ 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 } } -impl Database for MongoDB {} +impl<'a> Database for MongoDB<'a> {} -impl Helm for MongoDB { +impl<'a> Helm for MongoDB<'a> { fn helm_selector(&self) -> Option { self.selector() } @@ -251,7 +251,7 @@ impl Helm for MongoDB { } } -impl Terraform for MongoDB { +impl<'a> Terraform for MongoDB<'a> { fn terraform_common_resource_dir_path(&self) -> String { format!("{}/digitalocean/services/common", self.context.lib_root_dir()) } @@ -261,7 +261,7 @@ impl Terraform for MongoDB { } } -impl Create for MongoDB { +impl<'a> Create for MongoDB<'a> { #[named] fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); @@ -270,10 +270,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, self.logger()) }) } @@ -283,25 +285,31 @@ 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(()) } } -impl Pause for MongoDB { +impl<'a> Pause for MongoDB<'a> { #[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,18 +323,21 @@ 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(()) } } -impl Delete for MongoDB { +impl<'a> Delete for MongoDB<'a> { #[named] fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); @@ -335,10 +346,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, self.logger()) }) } @@ -348,18 +361,21 @@ 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(()) } } -impl Listen for MongoDB { +impl<'a> Listen for MongoDB<'a> { fn listeners(&self) -> &Listeners { &self.listeners } diff --git a/src/cloud_provider/digitalocean/databases/mysql.rs b/src/cloud_provider/digitalocean/databases/mysql.rs index a43d54b0..3b9747ff 100644 --- a/src/cloud_provider/digitalocean/databases/mysql.rs +++ b/src/cloud_provider/digitalocean/databases/mysql.rs @@ -3,19 +3,20 @@ 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; -pub struct MySQL { +pub struct MySQL<'a> { context: Context, id: String, action: Action, @@ -28,9 +29,10 @@ pub struct MySQL { database_instance_type: String, options: DatabaseOptions, listeners: Listeners, + logger: &'a dyn Logger, } -impl MySQL { +impl<'a> MySQL<'a> { pub fn new( context: Context, id: &str, @@ -44,6 +46,7 @@ impl MySQL { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: &'a dyn Logger, ) -> Self { Self { context, @@ -58,11 +61,12 @@ 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) } fn cloud_provider_name(&self) -> &str { @@ -74,13 +78,13 @@ impl MySQL { } } -impl StatefulService for MySQL { +impl<'a> StatefulService for MySQL<'a> { fn is_managed_service(&self) -> bool { self.options.mode == MANAGED } } -impl ToTransmitter for MySQL { +impl<'a> ToTransmitter for MySQL<'a> { fn to_transmitter(&self) -> Transmitter { Transmitter::Database( self.id().to_string(), @@ -90,7 +94,7 @@ impl ToTransmitter for MySQL { } } -impl Service for MySQL { +impl<'a> Service for MySQL<'a> { fn context(&self) -> &Context { &self.context } @@ -152,17 +156,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 +173,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() { @@ -217,19 +217,11 @@ impl Service for MySQL { fn selector(&self) -> Option { 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(), - ) - } } -impl Database for MySQL {} +impl<'a> Database for MySQL<'a> {} -impl Helm for MySQL { +impl<'a> Helm for MySQL<'a> { fn helm_selector(&self) -> Option { self.selector() } @@ -251,7 +243,7 @@ impl Helm for MySQL { } } -impl Terraform for MySQL { +impl<'a> Terraform for MySQL<'a> { fn terraform_common_resource_dir_path(&self) -> String { format!("{}/digitalocean/services/common", self.context.lib_root_dir()) } @@ -261,7 +253,7 @@ impl Terraform for MySQL { } } -impl Create for MySQL { +impl<'a> Create for MySQL<'a> { #[named] fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); @@ -270,12 +262,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, self.logger())), ) } @@ -286,25 +280,31 @@ 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(()) } } -impl Pause for MySQL { +impl<'a> Pause for MySQL<'a> { #[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,18 +318,21 @@ 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(()) } } -impl Delete for MySQL { +impl<'a> Delete for MySQL<'a> { #[named] fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); @@ -338,12 +341,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, self.logger())), ) } @@ -353,17 +358,20 @@ 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(()) } } -impl Listen for MySQL { +impl<'a> Listen for MySQL<'a> { fn listeners(&self) -> &Listeners { &self.listeners } diff --git a/src/cloud_provider/digitalocean/databases/postgresql.rs b/src/cloud_provider/digitalocean/databases/postgresql.rs index ff1b194e..8f2ce2d4 100644 --- a/src/cloud_provider/digitalocean/databases/postgresql.rs +++ b/src/cloud_provider/digitalocean/databases/postgresql.rs @@ -3,19 +3,20 @@ 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; -pub struct PostgreSQL { +pub struct PostgreSQL<'a> { context: Context, id: String, action: Action, @@ -28,9 +29,10 @@ pub struct PostgreSQL { database_instance_type: String, options: DatabaseOptions, listeners: Listeners, + logger: &'a dyn Logger, } -impl PostgreSQL { +impl<'a> PostgreSQL<'a> { pub fn new( context: Context, id: &str, @@ -44,6 +46,7 @@ impl PostgreSQL { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: &dyn Logger, ) -> Self { PostgreSQL { context, @@ -58,11 +61,12 @@ 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) } fn cloud_provider_name(&self) -> &str { @@ -74,13 +78,13 @@ impl PostgreSQL { } } -impl StatefulService for PostgreSQL { +impl<'a> StatefulService for PostgreSQL<'a> { fn is_managed_service(&self) -> bool { self.options.mode == MANAGED } } -impl ToTransmitter for PostgreSQL { +impl<'a> ToTransmitter for PostgreSQL<'a> { fn to_transmitter(&self) -> Transmitter { Transmitter::Database( self.id().to_string(), @@ -90,7 +94,7 @@ impl ToTransmitter for PostgreSQL { } } -impl Service for PostgreSQL { +impl<'a> Service for PostgreSQL<'a> { fn context(&self) -> &Context { &self.context } @@ -152,17 +156,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 +173,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,18 +220,14 @@ 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 } } -impl Database for PostgreSQL {} +impl<'a> Database for PostgreSQL<'a> {} -impl Helm for PostgreSQL { +impl<'a> Helm for PostgreSQL<'a> { fn helm_selector(&self) -> Option { self.selector() } @@ -253,7 +249,7 @@ impl Helm for PostgreSQL { } } -impl Terraform for PostgreSQL { +impl<'a> Terraform for PostgreSQL<'a> { fn terraform_common_resource_dir_path(&self) -> String { format!("{}/digitalocean/services/common", self.context.lib_root_dir()) } @@ -263,7 +259,7 @@ impl Terraform for PostgreSQL { } } -impl Create for PostgreSQL { +impl<'a> Create for PostgreSQL<'a> { #[named] fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); @@ -272,12 +268,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, self.logger())), ) } @@ -287,25 +285,31 @@ 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(()) } } -impl Pause for PostgreSQL { +impl<'a> Pause for PostgreSQL<'a> { #[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,18 +323,21 @@ 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(()) } } -impl Delete for PostgreSQL { +impl<'a> Delete for PostgreSQL<'a> { #[named] fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); @@ -339,12 +346,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, self.logger())), ) } @@ -354,18 +363,21 @@ 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(()) } } -impl Listen for PostgreSQL { +impl<'a> Listen for PostgreSQL<'a> { fn listeners(&self) -> &Listeners { &self.listeners } diff --git a/src/cloud_provider/digitalocean/databases/redis.rs b/src/cloud_provider/digitalocean/databases/redis.rs index 148e449d..163c6f43 100644 --- a/src/cloud_provider/digitalocean/databases/redis.rs +++ b/src/cloud_provider/digitalocean/databases/redis.rs @@ -3,19 +3,20 @@ 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; -pub struct Redis { +pub struct Redis<'a> { context: Context, id: String, action: Action, @@ -28,9 +29,10 @@ pub struct Redis { database_instance_type: String, options: DatabaseOptions, listeners: Listeners, + logger: &'a dyn Logger, } -impl Redis { +impl<'a> Redis<'a> { pub fn new( context: Context, id: &str, @@ -44,6 +46,7 @@ impl Redis { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: &'a dyn Logger, ) -> Self { Self { context, @@ -58,11 +61,12 @@ 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) } fn cloud_provider_name(&self) -> &str { @@ -74,13 +78,13 @@ impl Redis { } } -impl StatefulService for Redis { +impl<'a> StatefulService for Redis<'a> { fn is_managed_service(&self) -> bool { self.options.mode == MANAGED } } -impl ToTransmitter for Redis { +impl<'a> ToTransmitter for Redis<'a> { fn to_transmitter(&self) -> Transmitter { Transmitter::Database( self.id().to_string(), @@ -90,7 +94,7 @@ impl ToTransmitter for Redis { } } -impl Service for Redis { +impl<'a> Service for Redis<'a> { fn context(&self) -> &Context { &self.context } @@ -152,17 +156,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 +171,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,18 +217,14 @@ 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 } } -impl Database for Redis {} +impl<'a> Database for Redis<'a> {} -impl Helm for Redis { +impl<'a> Helm for Redis<'a> { fn helm_selector(&self) -> Option { self.selector() } @@ -250,7 +246,7 @@ impl Helm for Redis { } } -impl Terraform for Redis { +impl<'a> Terraform for Redis<'a> { fn terraform_common_resource_dir_path(&self) -> String { format!("{}/digitalocean/services/common", self.context.lib_root_dir()) } @@ -260,7 +256,7 @@ impl Terraform for Redis { } } -impl Create for Redis { +impl<'a> Create for Redis<'a> { #[named] fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); @@ -269,12 +265,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, self.logger())), ) } @@ -285,24 +283,30 @@ 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(()) } } -impl Pause for Redis { +impl<'a> Pause for Redis<'a> { #[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,17 +320,20 @@ 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(()) } } -impl Delete for Redis { +impl<'a> Delete for Redis<'a> { #[named] fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); @@ -335,12 +342,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, self.logger())), ) } @@ -350,17 +359,20 @@ 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(()) } } -impl Listen for Redis { +impl<'a> Listen for Redis<'a> { fn listeners(&self) -> &Listeners { &self.listeners } diff --git a/src/cloud_provider/digitalocean/mod.rs b/src/cloud_provider/digitalocean/mod.rs index 8c094fbf..d1ffd72f 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}; +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 { 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 b695c9e3..d8eff19e 100644 --- a/src/cloud_provider/digitalocean/router.rs +++ b/src/cloud_provider/digitalocean/router.rs @@ -3,18 +3,18 @@ use tera::Context as TeraContext; 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::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; -pub struct Router { +pub struct Router<'a> { context: Context, id: String, action: Action, @@ -24,9 +24,10 @@ pub struct Router { sticky_sessions_enabled: bool, routes: Vec, listeners: Listeners, + logger: &'a dyn Logger, } -impl Router { +impl<'a> Router<'a> { pub fn new( context: Context, id: &str, @@ -37,6 +38,7 @@ impl Router { routes: Vec, sticky_sessions_enabled: bool, listeners: Listeners, + logger: &'a dyn Logger, ) -> Self { Router { context, @@ -48,6 +50,7 @@ impl Router { sticky_sessions_enabled, routes, listeners, + logger, } } @@ -60,7 +63,7 @@ impl Router { } } -impl Service for Router { +impl<'a> Service for Router<'a> { fn context(&self) -> &Context { &self.context } @@ -122,6 +125,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); @@ -185,34 +189,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()); @@ -242,12 +256,12 @@ 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 } } -impl crate::cloud_provider::service::Router for Router { +impl<'a> crate::cloud_provider::service::Router for Router<'a> { fn domains(&self) -> Vec<&str> { let mut _domains = vec![self.default_domain.as_str()]; @@ -263,7 +277,7 @@ impl crate::cloud_provider::service::Router for Router { } } -impl Helm for Router { +impl<'a> Helm for Router<'a> { fn helm_selector(&self) -> Option { self.selector() } @@ -288,7 +302,7 @@ impl Helm for Router { } } -impl Listen for Router { +impl<'a> Listen for Router<'a> { fn listeners(&self) -> &Listeners { &self.listeners } @@ -298,34 +312,33 @@ impl Listen for Router { } } -impl StatelessService for Router {} +impl<'a> StatelessService for Router<'a> {} -impl ToTransmitter for Router { +impl<'a> ToTransmitter for Router<'a> { fn to_transmitter(&self) -> Transmitter { Transmitter::Router(self.id().to_string(), self.name().to_string()) } } -impl Create for Router { +impl<'a> Create for Router<'a> { #[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 @@ -335,13 +348,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 @@ -355,19 +367,17 @@ impl Create for Router { kubernetes.cloud_provider().credentials_environment_variables(), self.service_type(), ) - .map_err(|e| { - NewEngineError::new_helm_charts_upgrade_error(event_details.clone(), e).to_legacy_engine_error() - })?; + .map_err(|e| EngineError::new_helm_charts_upgrade_error(event_details.clone(), e).to_legacy_engine_error())?; if helm_history_row.is_none() || !helm_history_row.unwrap().is_successfully_deployed() { - return Err(self.engine_error(EngineErrorCause::Internal, "Router has failed to be deployed".into())); + return Err(EngineError::new_router_failed_to_deploy(event_details.clone())); } Ok(()) } 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()?; @@ -384,9 +394,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()), + ), + ), ); } } @@ -397,11 +417,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, || { @@ -410,14 +433,17 @@ impl Create for Router { } } -impl Pause for Router { +impl<'a> Pause for Router<'a> { #[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(()) } @@ -428,17 +454,20 @@ 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(()) } } -impl Delete for Router { +impl<'a> Delete for Router<'a> { #[named] fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); @@ -447,6 +476,8 @@ impl Delete for Router { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); delete_router(target, self, false, event_details) } @@ -463,6 +494,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/mod.rs b/src/cloud_provider/mod.rs index 0919f37c..05cc034f 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}; use crate::models::{Context, Listen}; pub mod aws; @@ -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 0a8baa51..82bfeb43 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, }; @@ -16,14 +17,13 @@ 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; -pub struct Application { +pub struct Application<'a> { context: Context, id: String, action: Action, @@ -39,9 +39,10 @@ pub struct Application { storage: Vec>, environment_variables: Vec, listeners: Listeners, + logger: &'a dyn Logger, } -impl Application { +impl<'a> Application<'a> { pub fn new( context: Context, id: &str, @@ -58,7 +59,8 @@ impl Application { storage: Vec>, environment_variables: Vec, listeners: Listeners, - ) -> Application { + logger: &dyn Logger, + ) -> Self { Application { context, id: id.to_string(), @@ -75,6 +77,7 @@ impl Application { storage, environment_variables, listeners, + logger, } } @@ -91,7 +94,7 @@ impl Application { } } -impl crate::cloud_provider::service::Application for Application { +impl<'a> crate::cloud_provider::service::Application for Application<'a> { fn image(&self) -> &Image { &self.image } @@ -101,7 +104,7 @@ impl crate::cloud_provider::service::Application for Application { } } -impl Helm for Application { +impl<'a> Helm for Application<'a> { fn helm_selector(&self) -> Option { self.selector() } @@ -123,15 +126,15 @@ impl Helm for Application { } } -impl StatelessService for Application {} +impl<'a> StatelessService for Application<'a> {} -impl ToTransmitter for Application { +impl<'a> ToTransmitter for Application<'a> { fn to_transmitter(&self) -> Transmitter { Transmitter::Application(self.id().to_string(), self.name().to_string()) } } -impl Service for Application { +impl<'a> Service for Application<'a> { fn context(&self) -> &Context { &self.context } @@ -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,23 +322,26 @@ 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 { +impl<'a> Create for Application<'a> { #[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) + deploy_user_stateless_service(target, self, event_details) }) } @@ -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, || { @@ -349,14 +367,17 @@ impl Create for Application { } } -impl Pause for Application { +impl<'a> Pause for Application<'a> { #[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,18 +396,21 @@ 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(()) } } -impl Delete for Application { +impl<'a> Delete for Application<'a> { #[named] fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); @@ -395,10 +419,12 @@ 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, || { - delete_stateless_service(target, self, false, event_details.clone()) + delete_stateless_service(target, self, false, event_details) }) } @@ -414,15 +440,17 @@ 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, || { - delete_stateless_service(target, self, true, event_details.clone()) + delete_stateless_service(target, self, true, event_details) }) } } -impl Listen for Application { +impl<'a> Listen for Application<'a> { fn listeners(&self) -> &Listeners { &self.listeners } diff --git a/src/cloud_provider/scaleway/databases/mongodb.rs b/src/cloud_provider/scaleway/databases/mongodb.rs index e8c9fd76..6eb3600f 100644 --- a/src/cloud_provider/scaleway/databases/mongodb.rs +++ b/src/cloud_provider/scaleway/databases/mongodb.rs @@ -3,19 +3,20 @@ 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::errors::EngineError; -use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter}; +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; -pub struct MongoDB { +pub struct MongoDB<'a> { context: Context, id: String, action: Action, @@ -28,9 +29,10 @@ pub struct MongoDB { database_instance_type: String, options: DatabaseOptions, listeners: Listeners, + logger: &'a dyn Logger, } -impl MongoDB { +impl<'a> MongoDB<'a> { pub fn new( context: Context, id: &str, @@ -44,6 +46,7 @@ impl MongoDB { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: &'a dyn Logger, ) -> Self { MongoDB { context, @@ -58,11 +61,12 @@ 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) } fn cloud_provider_name(&self) -> &str { @@ -74,13 +78,13 @@ impl MongoDB { } } -impl StatefulService for MongoDB { +impl<'a> StatefulService for MongoDB<'a> { fn is_managed_service(&self) -> bool { self.options.mode == MANAGED } } -impl ToTransmitter for MongoDB { +impl<'a> ToTransmitter for MongoDB<'a> { fn to_transmitter(&self) -> Transmitter { Transmitter::Database( self.id().to_string(), @@ -90,7 +94,7 @@ impl ToTransmitter for MongoDB { } } -impl Service for MongoDB { +impl<'a> Service for MongoDB<'a> { fn context(&self) -> &Context { &self.context } @@ -152,6 +156,7 @@ 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; @@ -170,7 +175,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() { @@ -211,14 +216,18 @@ impl Service for MongoDB { Ok(context) } + fn logger(&self) -> &dyn Logger { + self.logger + } + fn selector(&self) -> Option { Some(format!("app={}", self.sanitized_name())) } } -impl Database for MongoDB {} +impl<'a> Database for MongoDB<'a> {} -impl Helm for MongoDB { +impl<'a> Helm for MongoDB<'a> { fn helm_selector(&self) -> Option { self.selector() } @@ -240,7 +249,7 @@ impl Helm for MongoDB { } } -impl Terraform for MongoDB { +impl<'a> Terraform for MongoDB<'a> { fn terraform_common_resource_dir_path(&self) -> String { format!("{}/scaleway/services/common", self.context.lib_root_dir()) } @@ -250,7 +259,7 @@ impl Terraform for MongoDB { } } -impl Create for MongoDB { +impl<'a> Create for MongoDB<'a> { #[named] fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); @@ -259,10 +268,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, self.logger()) }) } @@ -272,24 +283,30 @@ 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(()) } } -impl Pause for MongoDB { +impl<'a> Pause for MongoDB<'a> { #[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, || { @@ -303,18 +320,21 @@ 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(()) } } -impl Delete for MongoDB { +impl<'a> Delete for MongoDB<'a> { #[named] fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); @@ -323,10 +343,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, self.logger()) }) } @@ -336,17 +358,20 @@ 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(()) } } -impl Listen for MongoDB { +impl<'a> Listen for MongoDB<'a> { fn listeners(&self) -> &Listeners { &self.listeners } diff --git a/src/cloud_provider/scaleway/databases/mysql.rs b/src/cloud_provider/scaleway/databases/mysql.rs index b2fd053a..08729451 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,15 +11,16 @@ 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 { +pub struct MySQL<'a> { context: Context, id: String, action: Action, @@ -32,9 +33,10 @@ pub struct MySQL { database_instance_type: String, options: DatabaseOptions, listeners: Listeners, + logger: &'a dyn Logger, } -impl MySQL { +impl<'a> MySQL<'a> { pub fn new( context: Context, id: &str, @@ -48,6 +50,7 @@ impl MySQL { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: &dyn Logger, ) -> Self { Self { context, @@ -62,21 +65,23 @@ 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, + ) } - 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(); @@ -105,13 +110,13 @@ impl MySQL { } } -impl StatefulService for MySQL { +impl<'a> StatefulService for MySQL<'a> { fn is_managed_service(&self) -> bool { self.options.mode == MANAGED } } -impl ToTransmitter for MySQL { +impl<'a> ToTransmitter for MySQL<'a> { fn to_transmitter(&self) -> Transmitter { Transmitter::Database( self.id().to_string(), @@ -121,7 +126,7 @@ impl ToTransmitter for MySQL { } } -impl Service for MySQL { +impl<'a> Service for MySQL<'a> { fn context(&self) -> &Context { &self.context } @@ -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,18 +259,14 @@ 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 } } -impl Database for MySQL {} +impl<'a> Database for MySQL<'a> {} -impl Helm for MySQL { +impl<'a> Helm for MySQL<'a> { fn helm_selector(&self) -> Option { self.selector() } @@ -289,7 +288,7 @@ impl Helm for MySQL { } } -impl Terraform for MySQL { +impl<'a> Terraform for MySQL<'a> { fn terraform_common_resource_dir_path(&self) -> String { format!("{}/scaleway/services/common", self.context.lib_root_dir()) } @@ -299,7 +298,7 @@ impl Terraform for MySQL { } } -impl Create for MySQL { +impl<'a> Create for MySQL<'a> { #[named] fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); @@ -308,10 +307,12 @@ 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, self.logger()) }) } @@ -321,25 +322,31 @@ 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(()) } } -impl Pause for MySQL { +impl<'a> Pause for MySQL<'a> { #[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,18 +360,21 @@ 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(()) } } -impl Delete for MySQL { +impl<'a> Delete for MySQL<'a> { #[named] fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); @@ -373,10 +383,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, self.logger()) }) } @@ -386,17 +398,20 @@ 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(()) } } -impl Listen for MySQL { +impl<'a> Listen for MySQL<'a> { fn listeners(&self) -> &Listeners { &self.listeners } diff --git a/src/cloud_provider/scaleway/databases/postgresql.rs b/src/cloud_provider/scaleway/databases/postgresql.rs index 81626556..3de40074 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,15 +11,16 @@ 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 { +pub struct PostgreSQL<'a> { context: Context, id: String, action: Action, @@ -32,9 +33,10 @@ pub struct PostgreSQL { database_instance_type: String, options: DatabaseOptions, listeners: Listeners, + logger: &'a dyn Logger, } -impl PostgreSQL { +impl<'a> PostgreSQL<'a> { pub fn new( context: Context, id: &str, @@ -48,6 +50,7 @@ impl PostgreSQL { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: &'a dyn Logger, ) -> Self { Self { context, @@ -62,21 +65,23 @@ 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, + ) } - 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(); @@ -114,13 +119,13 @@ impl PostgreSQL { } } -impl StatefulService for PostgreSQL { +impl<'a> StatefulService for PostgreSQL<'a> { fn is_managed_service(&self) -> bool { self.options.mode == MANAGED } } -impl ToTransmitter for PostgreSQL { +impl<'a> ToTransmitter for PostgreSQL<'a> { fn to_transmitter(&self) -> Transmitter { Transmitter::Database( self.id().to_string(), @@ -130,7 +135,7 @@ impl ToTransmitter for PostgreSQL { } } -impl Service for PostgreSQL { +impl<'a> Service for PostgreSQL<'a> { fn context(&self) -> &Context { &self.context } @@ -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,18 +268,14 @@ 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 } } -impl Database for PostgreSQL {} +impl<'a> Database for PostgreSQL<'a> {} -impl Helm for PostgreSQL { +impl<'a> Helm for PostgreSQL<'a> { fn helm_selector(&self) -> Option { self.selector() } @@ -298,7 +297,7 @@ impl Helm for PostgreSQL { } } -impl Terraform for PostgreSQL { +impl<'a> Terraform for PostgreSQL<'a> { fn terraform_common_resource_dir_path(&self) -> String { format!("{}/scaleway/services/common", self.context.lib_root_dir()) } @@ -308,7 +307,7 @@ impl Terraform for PostgreSQL { } } -impl Create for PostgreSQL { +impl<'a> Create for PostgreSQL<'a> { #[named] fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); @@ -317,10 +316,12 @@ 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, self.logger()) }) } @@ -330,25 +331,31 @@ 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(()) } } -impl Pause for PostgreSQL { +impl<'a> Pause for PostgreSQL<'a> { #[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,18 +369,21 @@ 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(()) } } -impl Delete for PostgreSQL { +impl<'a> Delete for PostgreSQL<'a> { #[named] fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); @@ -382,10 +392,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, self.logger()) }) } @@ -395,17 +407,20 @@ 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(()) } } -impl Listen for PostgreSQL { +impl<'a> Listen for PostgreSQL<'a> { fn listeners(&self) -> &Listeners { &self.listeners } diff --git a/src/cloud_provider/scaleway/databases/redis.rs b/src/cloud_provider/scaleway/databases/redis.rs index 5e371b24..ef4bddfe 100644 --- a/src/cloud_provider/scaleway/databases/redis.rs +++ b/src/cloud_provider/scaleway/databases/redis.rs @@ -3,19 +3,20 @@ 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; -pub struct Redis { +pub struct Redis<'a> { context: Context, id: String, action: Action, @@ -28,9 +29,10 @@ pub struct Redis { database_instance_type: String, options: DatabaseOptions, listeners: Listeners, + logger: &'a dyn Logger, } -impl Redis { +impl<'a> Redis<'a> { pub fn new( context: Context, id: &str, @@ -44,6 +46,7 @@ impl Redis { database_instance_type: &str, options: DatabaseOptions, listeners: Listeners, + logger: &'a dyn Logger, ) -> Self { Self { context, @@ -58,11 +61,12 @@ 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) } fn cloud_provider_name(&self) -> &str { @@ -74,13 +78,13 @@ impl Redis { } } -impl StatefulService for Redis { +impl<'a> StatefulService for Redis<'a> { fn is_managed_service(&self) -> bool { self.options.mode == MANAGED } } -impl ToTransmitter for Redis { +impl<'a> ToTransmitter for Redis<'a> { fn to_transmitter(&self) -> Transmitter { Transmitter::Database( self.id().to_string(), @@ -90,7 +94,7 @@ impl ToTransmitter for Redis { } } -impl Service for Redis { +impl<'a> Service for Redis<'a> { fn context(&self) -> &Context { &self.context } @@ -152,18 +156,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 +172,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,18 +218,14 @@ 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 } } -impl Database for Redis {} +impl<'a> Database for Redis<'a> {} -impl Helm for Redis { +impl<'a> Helm for Redis<'a> { fn helm_selector(&self) -> Option { self.selector() } @@ -251,7 +247,7 @@ impl Helm for Redis { } } -impl Terraform for Redis { +impl<'a> Terraform for Redis<'a> { fn terraform_common_resource_dir_path(&self) -> String { format!("{}/scaleway/services/common", self.context.lib_root_dir()) } @@ -261,7 +257,7 @@ impl Terraform for Redis { } } -impl Create for Redis { +impl<'a> Create for Redis<'a> { #[named] fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy)); @@ -270,10 +266,12 @@ 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, self.logger()) }) } @@ -283,24 +281,30 @@ 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(()) } } -impl Pause for Redis { +impl<'a> Pause for Redis<'a> { #[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,17 +318,20 @@ 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(()) } } -impl Delete for Redis { +impl<'a> Delete for Redis<'a> { #[named] fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); @@ -333,10 +340,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, self.logger()) }) } @@ -346,17 +355,20 @@ 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(()) } } -impl Listen for Redis { +impl<'a> Listen for Redis<'a> { fn listeners(&self) -> &Listeners { &self.listeners } diff --git a/src/cloud_provider/scaleway/mod.rs b/src/cloud_provider/scaleway/mod.rs index 1a2c7c66..e0873ee8 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}; +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 { diff --git a/src/cloud_provider/scaleway/router.rs b/src/cloud_provider/scaleway/router.rs index 3769b7c1..08f70321 100644 --- a/src/cloud_provider/scaleway/router.rs +++ b/src/cloud_provider/scaleway/router.rs @@ -8,13 +8,13 @@ use crate::cloud_provider::service::{ use crate::cloud_provider::utilities::{check_cname_for, print_action, sanitize_name}; use crate::cloud_provider::DeploymentTarget; 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; -pub struct Router { +pub struct Router<'a> { context: Context, id: String, action: Action, @@ -24,9 +24,10 @@ pub struct Router { sticky_sessions_enabled: bool, routes: Vec, listeners: Listeners, + logger: &'a dyn Logger, } -impl Router { +impl<'a> Router<'a> { pub fn new( context: Context, id: &str, @@ -37,7 +38,8 @@ impl Router { routes: Vec, sticky_sessions_enabled: bool, listeners: Listeners, - ) -> Router { + logger: &'a dyn Logger, + ) -> Self { Router { context, id: id.to_string(), @@ -48,6 +50,7 @@ impl Router { sticky_sessions_enabled, routes, listeners, + logger, } } @@ -60,7 +63,7 @@ impl Router { } } -impl Service for Router { +impl<'a> Service for Router<'a> { fn context(&self) -> &Context { &self.context } @@ -122,6 +125,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; @@ -193,12 +197,12 @@ 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 } } -impl crate::cloud_provider::service::Router for Router { +impl<'a> crate::cloud_provider::service::Router for Router<'a> { fn domains(&self) -> Vec<&str> { let mut _domains = vec![self.default_domain.as_str()]; @@ -214,7 +218,7 @@ impl crate::cloud_provider::service::Router for Router { } } -impl Helm for Router { +impl<'a> Helm for Router<'a> { fn helm_selector(&self) -> Option { self.selector() } @@ -236,7 +240,7 @@ impl Helm for Router { } } -impl Listen for Router { +impl<'a> Listen for Router<'a> { fn listeners(&self) -> &Listeners { &self.listeners } @@ -246,34 +250,33 @@ impl Listen for Router { } } -impl StatelessService for Router {} +impl<'a> StatelessService for Router<'a> {} -impl ToTransmitter for Router { +impl<'a> ToTransmitter for Router<'a> { fn to_transmitter(&self) -> Transmitter { Transmitter::Router(self.id().to_string(), self.name().to_string()) } } -impl Create for Router { +impl<'a> Create for Router<'a> { #[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 @@ -283,13 +286,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 @@ -303,18 +305,18 @@ impl Create for Router { kubernetes.cloud_provider().credentials_environment_variables(), self.service_type(), ) - .map_err(|e| { - NewEngineError::new_helm_charts_upgrade_error(event_details.clone(), e).to_legacy_engine_error() - })?; + .map_err(|e| EngineError::new_helm_charts_upgrade_error(event_details.clone(), e).to_legacy_engine_error())?; if helm_history_row.is_none() || !helm_history_row.unwrap().is_successfully_deployed() { - return Err(self.engine_error(EngineErrorCause::Internal, "Router has failed to be deployed".into())); + return Err(EngineError::new_router_failed_to_deploy(event_details.clone())); } Ok(()) } 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()?; @@ -330,9 +332,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()), + ), + ), ); } } @@ -343,11 +355,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, || { @@ -356,14 +371,17 @@ impl Create for Router { } } -impl Pause for Router { +impl<'a> Pause for Router<'a> { #[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(()) } @@ -374,17 +392,20 @@ 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(()) } } -impl Delete for Router { +impl<'a> Delete for Router<'a> { #[named] fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> { let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete)); @@ -393,6 +414,8 @@ impl Delete for Router { self.struct_name(), function_name!(), self.name(), + event_details.clone(), + self.logger(), ); delete_router(target, self, false, event_details) } @@ -409,6 +432,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 eaade7c4..0342cee1 100644 --- a/src/cloud_provider/service.rs +++ b/src/cloud_provider/service.rs @@ -9,7 +9,7 @@ use tera::Context as TeraContext; use crate::build_platform::Image; use crate::cloud_provider::environment::Environment; use crate::cloud_provider::kubernetes::Kubernetes; -use crate::cloud_provider::utilities::{check_domain_for, VersionsNumber}; +use crate::cloud_provider::utilities::check_domain_for; use crate::cloud_provider::DeploymentTarget; use crate::cmd::helm::Timeout; use crate::cmd::kubectl::ScalingKind::Statefulset; diff --git a/src/cloud_provider/utilities.rs b/src/cloud_provider/utilities.rs index 947e6729..09467db8 100644 --- a/src/cloud_provider/utilities.rs +++ b/src/cloud_provider/utilities.rs @@ -1,6 +1,5 @@ use std::collections::HashMap; -use crate::cloud_provider::models::CpuLimits; use crate::error::{EngineError, StringError}; use crate::errors::CommandError; use crate::events::{EngineEvent, EventDetails, EventMessage}; @@ -14,7 +13,6 @@ 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}; diff --git a/src/container_registry/docker.rs b/src/container_registry/docker.rs index c7ee1325..f66e4314 100644 --- a/src/container_registry/docker.rs +++ b/src/container_registry/docker.rs @@ -1,7 +1,9 @@ use crate::cmd; use crate::cmd::utilities::QoveryCommand; use crate::container_registry::Kind; -use crate::error::{SimpleError, SimpleErrorKind}; +use crate::errors::CommandError; +use crate::events::{EngineEvent, EventDetails, EventMessage}; +use crate::logger::{LogLevel, Logger}; use chrono::Duration; use retry::delay::Fibonacci; use retry::Error::Operation; @@ -38,7 +40,9 @@ pub fn docker_manifest_inspect( image_name: String, image_tag: String, registry_url: String, -) -> Option { + event_details: EventDetails, + logger: &dyn Logger, +) -> Result { let image_with_tag = format!("{}:{}", image_name, image_tag); let registry_provider = match container_registry_kind { Kind::DockerHub => "DockerHub", @@ -62,26 +66,44 @@ pub fn docker_manifest_inspect( Ok(_) => { let joined = raw_output.join(""); match serde_json::from_str(&joined) { - Ok(extracted_manifest) => Some(extracted_manifest), + Ok(extracted_manifest) => Ok(extracted_manifest), Err(e) => { - error!( - "error while trying to deserialize manifest image manifest for image {} in {} ({}): {:?}", - image_with_tag, registry_provider, registry_url, e, + let error = CommandError::new( + e.to_string(), + Some(format!( + "Error while trying to deserialize manifest image manifest for image {} in {} ({}).", + image_with_tag, registry_provider, registry_url, + )), ); - None + + logger.log( + LogLevel::Warning, + EngineEvent::Warning(event_details.clone(), EventMessage::from(error.clone())), + ); + + Err(error) } } } Err(e) => { - error!( - "error while trying to inspect image manifest for image {} in {} ({}), command `{}`: {:?}", - image_with_tag, - registry_provider, - registry_url, - cmd::utilities::command_to_string(binary, &args, &envs), - e, + let error = CommandError::new( + format!( + "Command `{}`: {:?}", + cmd::utilities::command_to_string(binary, &args, &envs), + e + ), + Some(format!( + "Error while trying to inspect image manifest for image {} in {} ({}).", + image_with_tag, registry_provider, registry_url, + )), ); - None + + logger.log( + LogLevel::Warning, + EngineEvent::Warning(event_details.clone(), EventMessage::from(error.clone())), + ); + + Err(error) } }; } @@ -92,7 +114,9 @@ pub fn docker_login( registry_login: String, registry_pass: String, registry_url: String, -) -> Result<(), SimpleError> { + event_details: EventDetails, + logger: &dyn Logger, +) -> Result<(), CommandError> { let registry_provider = match container_registry_kind { Kind::DockerHub => "DockerHub", Kind::Ecr => "AWS ECR", @@ -114,16 +138,24 @@ pub fn docker_login( match cmd.exec() { Ok(_) => Ok(()), Err(e) => { - let error_message = format!( - "error while trying to login to registry {} {}, command `{}`: {:?}", - registry_provider, - registry_url, - cmd::utilities::command_to_string(binary, &args, &docker_envs), - e, + let err = CommandError::new( + format!( + "Command `{}`: {:?}", + cmd::utilities::command_to_string(binary, &args, &docker_envs), + e, + ), + Some(format!( + "Error while trying to login to registry {} {}.", + registry_provider, registry_url, + )), ); - error!("{}", error_message); - Err(SimpleError::new(SimpleErrorKind::Other, Some(error_message))) + logger.log( + LogLevel::Warning, + EngineEvent::Warning(event_details.clone(), EventMessage::from(err.clone())), + ); + + Err(err) } } } @@ -134,7 +166,9 @@ pub fn docker_tag_and_push_image( image_name: String, image_tag: String, dest: String, -) -> Result<(), SimpleError> { + event_details: EventDetails, + logger: &dyn Logger, +) -> Result<(), CommandError> { let image_with_tag = format!("{}:{}", image_name, image_tag); let registry_provider = match container_registry_kind { Kind::DockerHub => "DockerHub", @@ -143,19 +177,37 @@ pub fn docker_tag_and_push_image( Kind::ScalewayCr => "Scaleway Registry", }; - let mut cmd = QoveryCommand::new("docker", &vec!["tag", &image_with_tag, dest.as_str()], &docker_envs); + let binary = "docker"; + let args = vec!["tag", &image_with_tag, dest.as_str()]; + let mut cmd = QoveryCommand::new(binary, &args, &docker_envs); match retry::retry(Fibonacci::from_millis(3000).take(5), || match cmd.exec() { Ok(_) => OperationResult::Ok(()), Err(e) => { - info!("failed to tag image {}, retrying...", image_with_tag); + logger.log( + LogLevel::Warning, + EngineEvent::Warning( + event_details.clone(), + EventMessage::new( + format!( + "Command `{}`: {:?}", + cmd::utilities::command_to_string(binary, &args, &docker_envs), + e + ), + Some(format!("Failed to tag image {}, retrying...", image_with_tag)), + ), + ), + ); + OperationResult::Retry(e) } }) { Err(Operation { error, .. }) => { - return Err(SimpleError::new( - SimpleErrorKind::Other, - Some(format!("failed to tag image {}: {:?}", image_with_tag, error)), - )) + logger.log( + LogLevel::Warning, + EngineEvent::Warning(event_details.clone(), EventMessage::from(error.clone())), + ); + + Err(error) } _ => {} } @@ -169,24 +221,39 @@ pub fn docker_tag_and_push_image( ) { Ok(_) => OperationResult::Ok(()), Err(e) => { - warn!( - "failed to push image {} on {}, {:?} retrying...", - image_with_tag, registry_provider, e + logger.log( + LogLevel::Warning, + EngineEvent::Warning( + event_details.clone(), + EventMessage::new( + format!( + "Failed to push image {} on {}, retrying...", + image_with_tag, registry_provider + ), + Some(format!("{:?}", e)), + ), + ), ); OperationResult::Retry(e) } } }) { - Err(Operation { error, .. }) => Err(SimpleError::new(SimpleErrorKind::Other, Some(error.to_string()))), - Err(e) => Err(SimpleError::new( - SimpleErrorKind::Other, + Err(Operation { error, .. }) => Err(CommandError::new(error.to_string(), None)), + Err(e) => Err(CommandError::new( + format!("{:?}", e), Some(format!( - "unknown error while trying to push image {} to {}. {:?}", - image_with_tag, registry_provider, e + "Unknown error while trying to push image {} to {}.", + image_with_tag, registry_provider, )), )), _ => { - info!("image {} has successfully been pushed", image_with_tag); + logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(format!("image {} has successfully been pushed", image_with_tag)), + ), + ); Ok(()) } } @@ -196,7 +263,9 @@ pub fn docker_pull_image( container_registry_kind: Kind, docker_envs: Vec<(&str, &str)>, dest: String, -) -> Result<(), SimpleError> { + event_details: EventDetails, + logger: &dyn Logger, +) -> Result<(), CommandError> { let registry_provider = match container_registry_kind { Kind::DockerHub => "DockerHub", Kind::Ecr => "AWS ECR", @@ -213,31 +282,45 @@ pub fn docker_pull_image( ) { Ok(_) => OperationResult::Ok(()), Err(e) => { - warn!( - "failed to pull image from {} registry {}, {:?} retrying...", - registry_provider, - dest.as_str(), - e, + logger.log( + LogLevel::Warning, + EngineEvent::Warning( + event_details.clone(), + EventMessage::new( + format!( + "failed to pull image from {} registry {}, retrying...", + registry_provider, + dest.as_str(), + ), + Some(format!("{:?}", e)), + ), + ), ); + OperationResult::Retry(e) } } }) { - Err(Operation { error, .. }) => Err(SimpleError::new(SimpleErrorKind::Other, Some(error.to_string()))), - Err(e) => Err(SimpleError::new( - SimpleErrorKind::Other, + Err(Operation { error, .. }) => Err(CommandError::new(error.to_string(), None)), + Err(e) => Err(CommandError::new( + format!("{:?}", e), Some(format!( - "unknown error while trying to pull image {} from {} registry. {:?}", + "Unknown error while trying to pull image {} from {} registry.", dest.as_str(), registry_provider, - e, )), )), _ => { - info!( - "image {} has successfully been pulled from {} registry", - dest.as_str(), - registry_provider, + logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(format!( + "Image {} has successfully been pulled from {} registry", + dest.as_str(), + registry_provider, + )), + ), ); Ok(()) } diff --git a/src/container_registry/docker_hub.rs b/src/container_registry/docker_hub.rs index f7d67c65..7a088216 100644 --- a/src/container_registry/docker_hub.rs +++ b/src/container_registry/docker_hub.rs @@ -6,22 +6,25 @@ use crate::build_platform::Image; use crate::cmd::utilities::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::CommandError; +use crate::events::{EngineEvent, EventMessage}; +use crate::logger::{LogLevel, Logger}; use crate::models::{ Context, Listen, Listener, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope, }; -pub struct DockerHub { +pub struct DockerHub<'a> { context: Context, id: String, name: String, login: String, password: String, listeners: Listeners, + logger: &'a dyn Logger, } -impl DockerHub { - pub fn new(context: Context, id: &str, name: &str, login: &str, password: &str) -> Self { +impl<'a> DockerHub<'a> { + pub fn new(context: Context, id: &str, name: &str, login: &str, password: &str, logger: &'a dyn Logger) -> Self { DockerHub { context, id: id.to_string(), @@ -29,10 +32,13 @@ impl DockerHub { login: login.to_string(), password: password.to_string(), listeners: vec![], + logger, } } pub fn exec_docker_login(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(); + let envs = match self.context.docker_tcp_socket() { Some(tcp_socket) => vec![("DOCKER_HOST", tcp_socket.as_str())], None => vec![], @@ -46,27 +52,25 @@ impl DockerHub { match cmd.exec() { Ok(_) => Ok(()), - Err(_) => Err(self.engine_error( - EngineErrorCause::User( - "Your DockerHub account seems to be no longer valid (bad Credentials). \ - Please contact your Organization administrator to fix or change the Credentials.", - ), - format!("failed to login to DockerHub {}", self.name_with_id()), + Err(_) => Err(EngineError::new_client_invalid_cloud_provider_credentials( + event_details, )), } } fn pull_image(&self, dest: String, image: &Image) -> Result { - match docker_pull_image(self.kind(), vec![], dest.clone()) { + let event_details = self.get_event_details(); + match docker_pull_image(self.kind(), vec![], dest.clone(), event_details.clone(), self.logger) { Ok(_) => { let mut image = image.clone(); image.registry_url = Some(dest); Ok(PullResult::Some(image)) } - Err(e) => Err(self.engine_error( - EngineErrorCause::Internal, - e.message - .unwrap_or_else(|| "unknown error occurring during docker pull".to_string()), + Err(e) => Err(EngineError::new_docker_pull_image_error( + event_details, + image.name.to_string(), + dest.to_string(), + e, )), } } @@ -90,15 +94,6 @@ impl ContainerRegistry for DockerHub { } 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); Ok(()) } @@ -119,6 +114,7 @@ impl ContainerRegistry for DockerHub { } fn does_image_exists(&self, image: &Image) -> bool { + let event_details = self.get_event_details(); use reqwest::blocking::Client; let client = Client::new(); let path = format!( @@ -133,13 +129,27 @@ impl ContainerRegistry for DockerHub { match res { Ok(out) => matches!(out.status(), StatusCode::OK), Err(e) => { - error!("While trying to retrieve if DockerHub repository exist {:?}", e); + self.logger.log( + LogLevel::Error, + EngineEvent::Error( + EngineError::new_container_registry_repository_doesnt_exist( + event_details.clone(), + image.name.to_string(), + Some(CommandError::new( + e.to_string(), + Some("Error while trying to retrieve if DockerHub repository exist.".to_string()), + )), + ), + None, + ), + ); false } } } fn pull(&self, image: &Image) -> Result { + let event_details = self.get_event_details(); let listeners_helper = ListenersHelper::new(&self.listeners); if !self.does_image_exists(image) { @@ -148,7 +158,14 @@ impl ContainerRegistry for DockerHub { image, self.name() ); - info!("{}", info_message.as_str()); + + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(info_message.to_string()), + ), + ); listeners_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Application { @@ -163,7 +180,14 @@ impl ContainerRegistry for DockerHub { } let info_message = format!("pull image {:?} from DockerHub {} repository", image, self.name()); - info!("{}", info_message.as_str()); + + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(info_message.to_string()), + ), + ); listeners_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Application { @@ -183,6 +207,8 @@ impl ContainerRegistry for DockerHub { } fn push(&self, image: &Image, force_push: bool) -> Result { + let event_details = self.get_event_details(); + let _ = self.exec_docker_login()?; let dest = format!("{}/{}", self.login.as_str(), image.name_with_tag().as_str()); @@ -196,7 +222,13 @@ impl ContainerRegistry for DockerHub { self.name() ); - info!("{}", info_message.as_str()); + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(info_message.to_string()), + ), + ); listeners_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Application { @@ -219,6 +251,14 @@ impl ContainerRegistry for DockerHub { self.name() ); + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(info_message.to_string()), + ), + ); + listeners_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Application { id: image.application_id.clone(), @@ -228,16 +268,25 @@ impl ContainerRegistry for DockerHub { self.context.execution_id(), )); - match docker_tag_and_push_image(self.kind(), vec![], image.name.clone(), image.tag.clone(), dest.clone()) { + match docker_tag_and_push_image( + self.kind(), + vec![], + image.name.clone(), + image.tag.clone(), + dest.clone(), + event_details.clone(), + self.logger, + ) { Ok(_) => { let mut image = image.clone(); image.registry_url = Some(dest); Ok(PushResult { image }) } - Err(e) => Err(self.engine_error( - EngineErrorCause::Internal, - e.message - .unwrap_or_else(|| "unknown error occurring during docker push".to_string()), + Err(e) => Err(EngineError::new_docker_push_image_error( + event_details.clone(), + image.name.to_string(), + dest.to_string(), + e, )), } } diff --git a/src/container_registry/docr.rs b/src/container_registry/docr.rs index 349bd8b1..5cd5efe1 100644 --- a/src/container_registry/docr.rs +++ b/src/container_registry/docr.rs @@ -7,7 +7,9 @@ use crate::build_platform::Image; use crate::cmd::utilities::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::CommandError; +use crate::events::{EngineEvent, EventDetails, EventMessage}; +use crate::logger::{LogLevel, Logger}; use crate::models::{ Context, Listen, Listener, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope, }; @@ -22,40 +24,42 @@ const CR_CLUSTER_API_PATH: &str = "https://api.digitalocean.com/v2/kubernetes/re // TODO : use --output json // see https://www.digitalocean.com/community/tutorials/how-to-use-doctl-the-official-digitalocean-command-line-client -pub struct DOCR { +pub struct DOCR<'a> { pub context: Context, pub name: String, pub api_key: String, pub id: String, pub listeners: Listeners, + logger: &'a dyn Logger, } -impl DOCR { - pub fn new(context: Context, id: &str, name: &str, api_key: &str) -> Self { +impl<'a> DOCR<'a> { + pub fn new(context: Context, id: &str, name: &str, api_key: &str, logger: &dyn Logger) -> Self { DOCR { context, name: name.into(), api_key: api_key.into(), id: id.into(), listeners: vec![], + logger, } } fn get_registry_name(&self, image: &Image) -> Result { + let event_details = self.get_event_details(); + let registry_name = match image.registry_name.as_ref() { // DOCR does not support upper cases Some(registry_name) => registry_name.to_lowercase(), - None => cast_simple_error_to_engine_error( - self.engine_error_scope(), - self.context().execution_id(), - get_current_registry_name(self.api_key.as_str()), - )?, + None => get_current_registry_name(self.api_key.as_str(), event_details)?, }; Ok(registry_name) } fn create_repository(&self, image: &Image) -> Result<(), EngineError> { + let event_details = self.get_event_details(); + let registry_name = match image.registry_name.as_ref() { // DOCR does not support upper cases Some(registry_name) => registry_name.to_lowercase(), @@ -83,46 +87,71 @@ impl DOCR { StatusCode::OK => Ok(()), StatusCode::CREATED => Ok(()), status => { - warn!("status from DO registry API {}", status); - return Err(self.engine_error( - EngineErrorCause::Internal, - format!( - "Bad status code : {} returned by the DO registry API for creating DO CR {}", + return Err(EngineError::new_container_registry_namespace_creation_error( + event_details.clone(), + self.name_with_id(), + registry_name.to_string(), + CommandError::new_from_safe_message(format!( + "Bad status code: `{}` returned by the DO registry API for creating DOCR `{}`.", status, registry_name.as_str(), - ), + )), )); } }, Err(e) => { - return Err(self.engine_error( - EngineErrorCause::Internal, - format!("failed to create DOCR repository {} : {:?}", registry_name.as_str(), e,), + return Err(EngineError::new_container_registry_namespace_creation_error( + event_details.clone(), + self.name_with_id(), + registry_name.to_string(), + CommandError::new( + e.to_string(), + Some(format!( + "Failed to create DOCR repository `{}`.", + registry_name.as_str(), + )), + ), )); } } } Err(e) => { - return Err(self.engine_error( - EngineErrorCause::Internal, - format!("Unable to initialize DO Registry {} : {:?}", registry_name.as_str(), e,), + return Err(EngineError::new_container_registry_namespace_creation_error( + event_details.clone(), + self.name_with_id(), + registry_name.to_string(), + CommandError::new( + e.to_string(), + Some(format!( + "Failed to create DOCR repository `{}`.", + registry_name.as_str(), + )), + ), )); } } } fn push_image(&self, registry_name: String, dest: String, image: &Image) -> Result { - let _ = - match docker_tag_and_push_image(self.kind(), vec![], image.name.clone(), image.tag.clone(), dest.clone()) { - Ok(_) => {} - Err(e) => { - return Err(self.engine_error( - EngineErrorCause::Internal, - e.message - .unwrap_or_else(|| "unknown error occurring during docker push".to_string()), - )); - } - }; + let event_details = self.get_event_details(); + + match docker_tag_and_push_image( + self.kind(), + vec![], + image.name.clone(), + image.tag.clone(), + dest.clone(), + event_details.clone(), + self.logger, + ) { + Ok(_) => {} + Err(e) => Err(EngineError::new_docker_push_image_error( + event_details, + image.name.to_string(), + dest.to_string(), + e, + )), + } let mut image = image.clone(); image.registry_name = Some(registry_name.clone()); @@ -134,15 +163,23 @@ impl DOCR { match self.does_image_exists(&image) { true => OperationResult::Ok(&image), false => { - warn!("image is not yet available on Digital Ocean Registry, retrying in a few seconds..."); + self.logger.log( + LogLevel::Warning, + EngineEvent::Warning( + self.get_event_details(), + EventMessage::new_from_safe( + "Image is not yet available on DOCR, retrying in a few seconds...".to_string(), + ), + ), + ); OperationResult::Retry(()) } } }); - let image_not_reachable = Err(self.engine_error( - EngineErrorCause::Internal, - "image has been pushed on Digital Ocean Registry but is not yet available after 2min. Please try to redeploy in a few minutes".to_string(), + let image_not_reachable = Err(EngineError::new_container_registry_image_unreachable_after_push( + event_details.clone(), + image.name.to_string(), )); match result { Ok(_) => Ok(PushResult { image }), @@ -161,6 +198,8 @@ impl DOCR { } pub fn delete_repository(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(); + let headers = utilities::get_header_with_bearer(&self.api_key); let res = reqwest::blocking::Client::new() .delete(CR_API_PATH) @@ -171,26 +210,32 @@ impl DOCR { Ok(out) => match out.status() { StatusCode::NO_CONTENT => Ok(()), status => { - warn!("delete status from DO registry API {}", status); - return Err(self.engine_error( - EngineErrorCause::Internal, - format!( - "Bad status code : {} returned by the DO registry API for deleting DOCR repository", + return Err(EngineError::new_container_registry_delete_repository_error( + event_details.clone(), + "default".to_string(), // DO has only one repository + Some(CommandError::new_from_safe_message(format!( + "Bad status code: `{}` returned by the DO registry API for deleting DOCR.", status, - ), + ))), )); } }, Err(e) => { - return Err(self.engine_error( - EngineErrorCause::Internal, - format!("No response from the Digital Ocean API : {:?}", e), + return Err(EngineError::new_container_registry_delete_repository_error( + event_details.clone(), + "default".to_string(), // DO has only one repository + Some(CommandError::new( + e.to_string(), + Some("No response from the Digital Ocean API.".to_string()), + )), )); } } } pub fn exec_docr_login(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(); + let mut cmd = QoveryCommand::new( "doctl", &vec!["registry", "login", self.name.as_str(), "-t", self.api_key.as_str()], @@ -199,18 +244,16 @@ impl DOCR { match cmd.exec() { Ok(_) => Ok(()), - Err(_) => Err(self.engine_error( - EngineErrorCause::User( - "Your DOCR account seems to be no longer valid (bad Credentials). \ - Please contact your Organization administrator to fix or change the Credentials.", - ), - format!("failed to login to DOCR {}", self.name_with_id()), + Err(_) => Err(EngineError::new_client_invalid_cloud_provider_credentials( + event_details, )), } } fn pull_image(&self, registry_name: String, dest: String, image: &Image) -> Result { - match docker_pull_image(self.kind(), vec![], dest.clone()) { + let event_details = self.get_event_details(); + + match docker_pull_image(self.kind(), vec![], dest.clone(), event_details.clone(), self.logger()) { Ok(_) => { let mut image = image.clone(); image.registry_name = Some(registry_name.clone()); @@ -219,10 +262,11 @@ impl DOCR { image.registry_url = Some(dest); Ok(PullResult::Some(image)) } - Err(e) => Err(self.engine_error( - EngineErrorCause::Internal, - e.message - .unwrap_or_else(|| "unknown error occurring during docker pull".to_string()), + Err(e) => Err(EngineError::new_docker_pull_image_error( + event_details, + image.name.to_string(), + dest.to_string(), + e, )), } } @@ -266,10 +310,12 @@ impl ContainerRegistry for DOCR { } fn does_image_exists(&self, image: &Image) -> bool { + let event_details = self.get_event_details(); + let registry_name = match self.get_registry_name(image) { Ok(registry_name) => registry_name, Err(err) => { - warn!("{:?}", err); + self.logger.log(LogLevel::Error, EngineEvent::Error(err, None)); return false; } }; @@ -290,18 +336,38 @@ impl ContainerRegistry for DOCR { Ok(output) => match output.status() { StatusCode::OK => output.text(), _ => { - error!( - "While tyring to get all tags for image: {}, maybe this image not exist !", - &image.name + self.logger.log( + LogLevel::Error, + EngineEvent::Error( + EngineError::new_container_registry_image_doesnt_exist( + event_details.clone(), + image.name.to_string(), + Some(CommandError::new_from_safe_message(format!( + "While tyring to get all tags for image: `{}`, maybe this image not exist !", + image.name.to_string() + ))), + ), + None, + ), ); return false; } }, Err(_) => { - error!( - "While trying to communicate with DigitalOcean API to retrieve all tags for image {}", - &image.name + self.logger.log( + LogLevel::Error, + EngineEvent::Error( + EngineError::new_container_registry_image_doesnt_exist( + event_details.clone(), + image.name.to_string(), + Some(CommandError::new_from_safe_message(format!( + "While trying to communicate with DigitalOcean API to retrieve all tags for image `{}`.", + image.name.to_string() + ))), + ), + None, + ), ); return false; @@ -322,9 +388,22 @@ impl ContainerRegistry for DOCR { false } Err(_) => { - error!( - "Unable to deserialize tags from DigitalOcean API for image {}", - &image.tag + self.logger.log( + LogLevel::Error, + EngineEvent::Error( + EngineError::new_container_registry_image_doesnt_exist( + event_details.clone(), + image.name.to_string(), + Some(CommandError::new( + out.to_string(), + Some(format!( + "Unable to deserialize tags from DigitalOcean API for image {}", + &image.tag.to_string(), + )), + )), + ), + None, + ), ); false @@ -332,9 +411,19 @@ impl ContainerRegistry for DOCR { } } _ => { - error!( - "while retrieving tags for image {} Unable to get output from DigitalOcean API", - &image.name + self.logger.log( + LogLevel::Error, + EngineEvent::Error( + EngineError::new_container_registry_image_doesnt_exist( + event_details.clone(), + image.name.to_string(), + Some(CommandError::new_from_safe_message(format!( + "While retrieving tags for image `{}` Unable to get output from DigitalOcean API.", + image.name.to_string() + ))), + ), + None, + ), ); false @@ -343,11 +432,19 @@ impl ContainerRegistry for DOCR { } fn pull(&self, image: &Image) -> Result { + let event_details = self.get_event_details(); let listeners_helper = ListenersHelper::new(&self.listeners); if !self.does_image_exists(image) { let info_message = format!("image {:?} does not exist in DOCR {} repository", image, self.name()); - info!("{}", info_message.as_str()); + + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(info_message.to_string()), + ), + ); listeners_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Application { @@ -362,7 +459,14 @@ impl ContainerRegistry for DOCR { } let info_message = format!("pull image {:?} from DOCR {} repository", image, self.name()); - info!("{}", info_message.as_str()); + + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(info_message.to_string()), + ), + ); listeners_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Application { @@ -389,11 +493,27 @@ impl ContainerRegistry for DOCR { // https://www.digitalocean.com/docs/images/container-registry/how-to/use-registry-docker-kubernetes/ fn push(&self, image: &Image, force_push: bool) -> Result { + let event_details = self.get_event_details(); let registry_name = self.get_registry_name(image)?; match self.create_repository(image) { - Ok(_) => info!("DOCR {} has been created", registry_name.as_str()), - Err(_) => warn!("DOCR {} already exists", registry_name.as_str()), + Ok(_) => self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(format!("DOCR {} has been created", registry_name.as_str())), + ), + ), + Err(e) => self.logger.log( + LogLevel::Error, + EngineEvent::Error( + e.clone(), + Some(EventMessage::new_from_safe(format!( + "DOCR {} already exists", + registry_name.as_str() + ))), + ), + ), }; let _ = self.exec_docr_login()?; @@ -414,7 +534,13 @@ impl ContainerRegistry for DOCR { registry_name.as_str() ); - info!("{}", info_message.as_str()); + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(info_message.to_string()), + ), + ); listeners_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Application { @@ -439,6 +565,14 @@ impl ContainerRegistry for DOCR { image, registry_name ); + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(info_message.to_string()), + ), + ); + listeners_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Application { id: image.application_id.clone(), @@ -466,7 +600,7 @@ impl Listen for DOCR { } } -pub fn subscribe_kube_cluster_to_container_registry(api_key: &str, cluster_uuid: &str) -> Result<(), SimpleError> { +pub fn subscribe_kube_cluster_to_container_registry(api_key: &str, cluster_uuid: &str) -> Result<(), CommandError> { let headers = utilities::get_header_with_bearer(api_key); let cluster_ids = DoApiSubscribeToKubeCluster { cluster_uuids: vec![cluster_uuid.to_string()], @@ -484,31 +618,25 @@ pub fn subscribe_kube_cluster_to_container_registry(api_key: &str, cluster_uuid: match res { Ok(output) => match output.status() { StatusCode::NO_CONTENT => Ok(()), - status => { - warn!("status from DO registry API {}", status); - Err(SimpleError::new(SimpleErrorKind::Other, Some("Incorrect Status received from Digital Ocean when tyring to subscribe repository to cluster"))) - } + status => Err(CommandError::new_from_safe_message( + "Incorrect Status received from Digital Ocean when tyring to subscribe repository to cluster" + .to_string(), + )), }, - Err(e) => { - error!("{:?}", e); - Err(SimpleError::new( - SimpleErrorKind::Other, - Some("Unable to call Digital Ocean when tyring to subscribe repository to cluster"), - )) - } + Err(e) => Err(CommandError::new( + e.to_string(), + Some("Unable to call Digital Ocean when tyring to subscribe repository to cluster".to_string()), + )), } } - Err(e) => { - error!("{:?}", e); - Err(SimpleError::new( - SimpleErrorKind::Other, - Some("Unable to Serialize digital ocean cluster uuids"), - )) - } + Err(e) => Err(CommandError::new( + e.to_string(), + Some("Unable to Serialize digital ocean cluster uuids".to_string()), + )), }; } -pub fn get_current_registry_name(api_key: &str) -> Result { +pub fn get_current_registry_name(api_key: &str, event_details: EventDetails) -> Result { let headers = utilities::get_header_with_bearer(api_key); let res = reqwest::blocking::Client::new() .get(CR_API_PATH) @@ -523,28 +651,41 @@ pub fn get_current_registry_name(api_key: &str) -> Result { match res_registry { Ok(registry) => Ok(registry.registry.name), - Err(err) => Err(SimpleError::new( - SimpleErrorKind::Other, - Some(format!( - "An error occurred while deserializing JSON coming from Digital Ocean API: error: {:?}", - err + Err(err) => Err(EngineError::new_container_registry_repository_doesnt_exist( + event_details.clone(), + "default".to_string(), // DO has only one repository + Some(CommandError::new( + err.to_string(), + Some( + "An error occurred while deserializing JSON coming from Digital Ocean API.".to_string(), + ), )), )), } } status => { - warn!("status from Digital Ocean Registry API {}", status); - Err(SimpleError::new( - SimpleErrorKind::Other, - Some("Incorrect Status received from Digital Ocean when tyring to get container registry"), + Err(EngineError::new_container_registry_repository_doesnt_exist( + event_details.clone(), + "default".to_string(), // DO has only one repository + Some(CommandError::new( + format!("Status: {}", status), + Some( + "Incorrect Status received from Digital Ocean when tyring to get container registry." + .to_string(), + ), + )), )) } }, Err(e) => { error!("{:?}", e); - Err(SimpleError::new( - SimpleErrorKind::Other, - Some("Unable to call Digital Ocean when tyring to fetch the container registry name"), + Err(EngineError::new_container_registry_repository_doesnt_exist( + event_details.clone(), + "default".to_string(), // DO has only one repository + Some(CommandError::new( + e.to_string(), + Some("Unable to call Digital Ocean when tyring to fetch the container registry name.".to_string()), + )), )) } }; diff --git a/src/container_registry/ecr.rs b/src/container_registry/ecr.rs index 04b48d1a..1a3d0d2e 100644 --- a/src/container_registry/ecr.rs +++ b/src/container_registry/ecr.rs @@ -12,7 +12,9 @@ use crate::build_platform::Image; use crate::cmd::utilities::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::{CommandError, EngineError}; +use crate::events::{EngineEvent, EventMessage}; +use crate::logger::{LogLevel, Logger}; use crate::models::{ Context, Listen, Listener, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope, }; @@ -22,7 +24,7 @@ use retry::Error::Operation; use retry::OperationResult; use serde_json::json; -pub struct ECR { +pub struct ECR<'a> { context: Context, id: String, name: String, @@ -30,9 +32,10 @@ pub struct ECR { secret_access_key: String, region: Region, listeners: Listeners, + logger: &'a dyn Logger, } -impl ECR { +impl<'a> ECR<'a> { pub fn new( context: Context, id: &str, @@ -40,6 +43,7 @@ impl ECR { access_key_id: &str, secret_access_key: &str, region: &str, + logger: &'a dyn Logger, ) -> Self { ECR { context, @@ -49,6 +53,7 @@ impl ECR { secret_access_key: secret_access_key.to_string(), region: Region::from_str(region).unwrap(), listeners: vec![], + logger, } } @@ -115,6 +120,7 @@ impl ECR { fn push_image(&self, dest: String, image: &Image) -> Result { // READ https://docs.aws.amazon.com/AmazonECR/latest/userguide/docker-push-ecr-image.html // docker tag e9ae3c220b23 aws_account_id.dkr.ecr.region.amazonaws.com/my-web-app + let event_details = self.get_event_details(); match docker_tag_and_push_image( self.kind(), @@ -122,16 +128,19 @@ impl ECR { image.name.clone(), image.tag.clone(), dest.clone(), + event_details.clone(), + self.logger(), ) { Ok(_) => { let mut image = image.clone(); image.registry_url = Some(dest); Ok(PushResult { image }) } - Err(e) => Err(self.engine_error( - EngineErrorCause::Internal, - e.message - .unwrap_or_else(|| "unknown error occurring during docker push".to_string()), + Err(e) => Err(EngineError::new_docker_push_image_error( + event_details, + image.name.to_string(), + dest.to_string(), + e, )), } } @@ -139,24 +148,40 @@ impl ECR { fn pull_image(&self, dest: String, image: &Image) -> Result { // READ https://docs.aws.amazon.com/AmazonECR/latest/userguide/docker-pull-ecr-image.html // docker pull aws_account_id.dkr.ecr.us-west-2.amazonaws.com/amazonlinux:latest + let event_details = self.get_event_details(); - match docker_pull_image(self.kind(), self.docker_envs(), dest.clone()) { + match docker_pull_image( + self.kind(), + self.docker_envs(), + dest.clone(), + event_details.clone(), + self.logger(), + ) { Ok(_) => { let mut image = image.clone(); image.registry_url = Some(dest); Ok(PullResult::Some(image)) } - Err(e) => Err(self.engine_error( - EngineErrorCause::Internal, - e.message - .unwrap_or_else(|| "unknown error occurring during docker pull".to_string()), + Err(e) => Err(EngineError::new_docker_pull_image_error( + event_details, + image.name.to_string(), + dest.to_string(), + e, )), } } fn create_repository(&self, image: &Image) -> Result { + let event_details = self.get_event_details(); let repository_name = image.name.as_str(); - info!("creating ECR repository {}", &repository_name); + + self.logger().log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(format!("Creating ECR repository {}", &repository_name)), + ), + ); let mut repo_creation_counter = 0; let container_registry_request = DescribeRepositoriesRequest { @@ -176,7 +201,13 @@ impl ECR { .describe_repositories(container_registry_request.clone()), ) { Ok(x) => { - debug!("created {:?} repository", x); + self.logger().log( + LogLevel::Debug, + EngineEvent::Debug( + event_details.clone(), + EventMessage::new_from_safe(format!("Created {:?} repository", x)), + ), + ); OperationResult::Ok(()) } Err(e) => { @@ -184,40 +215,78 @@ impl ECR { RusotoError::Service(s) => match s { DescribeRepositoriesError::RepositoryNotFound(_) => { if repo_creation_counter != 0 { - warn!( - "repository {} was not found, {}x retrying...", - &repository_name, &repo_creation_counter + self.logger().log( + LogLevel::Warning, + EngineEvent::Warning( + event_details.clone(), + EventMessage::new_from_safe(format!( + "Repository {} was not found, {}x retrying...", + &repository_name, &repo_creation_counter + )), + ), ); } repo_creation_counter += 1; } - _ => warn!("{:?}", s), + _ => self.logger().log( + LogLevel::Warning, + EngineEvent::Warning( + event_details.clone(), + EventMessage::new( + "Error while trying to create repository.".to_string(), + Some(format!("{:?}", s)), + ), + ), + ), }, - _ => warn!("{:?}", e), + _ => self.logger().log( + LogLevel::Warning, + EngineEvent::Warning( + event_details.clone(), + EventMessage::new( + "Error while trying to create repository.".to_string(), + Some(format!("{:?}", e)), + ), + ), + ), } + // TODO: This behavior is weird, returning an ok message saying repository has been created in an error ... let msg = match block_on(self.ecr_client().create_repository(crr.clone())) { Ok(_) => format!("repository {} created", &repository_name), - Err(err) => format!( - "can't create ECR repository {} for {}. {:?}", - &repository_name, - self.name_with_id(), - err - ), + Err(err) => format!("{:?}", err), }; - OperationResult::Retry(Err(self.engine_error(EngineErrorCause::Internal, msg))) + OperationResult::Retry(Err(EngineError::new_container_registry_namespace_creation_error( + event_details.clone(), + repository_name.to_string(), + self.name_with_id(), + CommandError::new(msg.to_string(), Some("Can't create ECR repository".to_string())), + ))) } } }); match repo_created { - Ok(_) => info!( - "repository {} created after {} attempt(s)", - &repository_name, repo_creation_counter + Ok(_) => self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(format!( + "repository {} created after {} attempt(s)", + &repository_name, repo_creation_counter, + )), + ), ), Err(Operation { error, .. }) => return error, - Err(retry::Error::Internal(e)) => return Err(self.engine_error(EngineErrorCause::Internal, e)), + Err(retry::Error::Internal(e)) => { + return Err(EngineError::new_container_registry_namespace_creation_error( + event_details.clone(), + repository_name.to_string(), + self.name_with_id(), + CommandError::new_from_safe_message(e), + )) + } }; // apply retention policy @@ -250,32 +319,30 @@ impl ECR { }; match block_on(self.ecr_client().put_lifecycle_policy(plp)) { - Err(err) => { - error!( - "can't set lifecycle policy to ECR repository {} for {}: {}", - image.name.as_str(), - self.name_with_id(), - err - ); - - Err(self.engine_error( - EngineErrorCause::Internal, - format!( - "can't set lifecycle policy to ECR repository {} for {}", - image.name.as_str(), - self.name_with_id() - ), - )) - } + Err(err) => Err( + EngineError::new_container_registry_repository_set_lifecycle_policy_error( + event_details.clone(), + repository_name.to_string(), + CommandError::new_from_safe_message(err.to_string()), + ), + ), _ => Ok(self.get_repository(image).expect("cannot get repository")), } } fn get_or_create_repository(&self, image: &Image) -> Result { + let event_details = self.get_event_details(); + // check if the repository already exists let repository = self.get_repository(image); if repository.is_some() { - info!("ECR repository {} already exists", image.name.as_str()); + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(format!("ECR repository {} already exists", image.name.as_str())), + ), + ); return Ok(repository.unwrap()); } @@ -283,6 +350,7 @@ impl ECR { } fn get_credentials(&self) -> Result { + let event_details = self.get_event_details(); let r = block_on( self.ecr_client() .get_authorization_token(GetAuthorizationTokenRequest::default()), @@ -306,22 +374,16 @@ impl ECR { ) } None => { - return Err(self.engine_error( - EngineErrorCause::Internal, - format!( - "failed to retrieve credentials and endpoint URL from ECR {}", - self.name_with_id(), - ), + return Err(EngineError::new_container_registry_get_credentials_error( + event_details.clone(), + self.name_with_id(), )); } }, _ => { - return Err(self.engine_error( - EngineErrorCause::Internal, - format!( - "failed to retrieve credentials and endpoint URL from ECR {}", - self.name_with_id(), - ), + return Err(EngineError::new_container_registry_get_credentials_error( + event_details.clone(), + self.name_with_id(), )); } }; @@ -330,6 +392,7 @@ impl ECR { } fn exec_docker_login(&self) -> Result<(), EngineError> { + let event_details = self.get_event_details(); let credentials = self.get_credentials()?; let mut cmd = QoveryCommand::new( @@ -346,12 +409,8 @@ impl ECR { ); if let Err(_) = cmd.exec() { - return Err(self.engine_error( - EngineErrorCause::User( - "Your ECR account seems to be no longer valid (bad Credentials). \ - Please contact your Organization administrator to fix or change the Credentials.", - ), - format!("failed to login to ECR {}", self.name_with_id()), + return Err(EngineError::new_client_invalid_cloud_provider_credentials( + event_details.clone(), )); }; @@ -382,30 +441,54 @@ impl ContainerRegistry for ECR { 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(EngineError::new_client_invalid_cloud_provider_credentials( + self.get_event_details(), )), } } fn on_create(&self) -> Result<(), EngineError> { - info!("ECR.on_create() called"); + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + self.get_event_details(), + EventMessage::new_from_safe("ECR.on_create() called".to_string()), + ), + ); Ok(()) } fn on_create_error(&self) -> Result<(), EngineError> { + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + self.get_event_details(), + EventMessage::new_from_safe("ECR.on_create_error() called".to_string()), + ), + ); + unimplemented!() } fn on_delete(&self) -> Result<(), EngineError> { + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + self.get_event_details(), + EventMessage::new_from_safe("ECR.on_delete() called".to_string()), + ), + ); unimplemented!() } fn on_delete_error(&self) -> Result<(), EngineError> { + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + self.get_event_details(), + EventMessage::new_from_safe("ECR.on_delete_error() called".to_string()), + ), + ); unimplemented!() } @@ -414,11 +497,19 @@ impl ContainerRegistry for ECR { } fn pull(&self, image: &Image) -> Result { + let event_details = self.get_event_details(); let listeners_helper = ListenersHelper::new(&self.listeners); if !self.does_image_exists(image) { let info_message = format!("image {:?} does not exist in ECR {} repository", image, self.name()); - info!("{}", info_message.as_str()); + + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(info_message.to_string()), + ), + ); listeners_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Application { @@ -433,7 +524,14 @@ impl ContainerRegistry for ECR { } let info_message = format!("pull image {:?} from ECR {} repository", image, self.name()); - info!("{}", info_message.as_str()); + + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(info_message.to_string()), + ), + ); listeners_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Application { @@ -446,19 +544,7 @@ impl ContainerRegistry for ECR { let _ = self.exec_docker_login()?; - let repository = match self.get_or_create_repository(image) { - Ok(r) => r, - _ => { - return Err(self.engine_error( - EngineErrorCause::Internal, - format!( - "failed to create ECR repository for {} with image {:?}", - self.name_with_id(), - image, - ), - )); - } - }; + let repository = self.get_or_create_repository(image)?; let dest = format!("{}:{}", repository.repository_uri.unwrap(), image.tag.as_str()); @@ -469,23 +555,11 @@ impl ContainerRegistry for ECR { fn push(&self, image: &Image, force_push: bool) -> Result { let _ = self.exec_docker_login()?; - let repository = match if force_push { + let repository = if force_push { self.create_repository(image) } else { self.get_or_create_repository(image) - } { - Ok(r) => r, - _ => { - return Err(self.engine_error( - EngineErrorCause::Internal, - format!( - "failed to create ECR repository for {} with image {:?}", - self.name_with_id(), - image, - ), - )); - } - }; + }?; let dest = format!("{}:{}", repository.repository_uri.unwrap(), image.tag.as_str()); @@ -499,7 +573,13 @@ impl ContainerRegistry for ECR { self.name() ); - info!("{}", info_message.as_str()); + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + self.get_event_details(), + EventMessage::new_from_safe(info_message.to_string()), + ), + ); listeners_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Application { @@ -522,7 +602,13 @@ impl ContainerRegistry for ECR { self.name() ); - info!("{}", info_message.as_str()); + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + self.get_event_details(), + EventMessage::new_from_safe(info_message.to_string()), + ), + ); listeners_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Application { diff --git a/src/container_registry/mod.rs b/src/container_registry/mod.rs index e9c3950e..c9986270 100644 --- a/src/container_registry/mod.rs +++ b/src/container_registry/mod.rs @@ -1,8 +1,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; +use crate::events::{EnvironmentStep, EventDetails, Stage}; +use crate::models::{Context, Listen, QoveryIdentifier}; pub mod docker; pub mod docker_hub; @@ -27,15 +28,16 @@ pub trait ContainerRegistry: Listen { fn pull(&self, image: &Image) -> Result; fn push(&self, image: &Image, force_push: bool) -> Result; fn push_error(&self, image: &Image) -> Result; - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::ContainerRegistry(self.id().to_string(), self.name().to_string()) - } - fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError { - EngineError::new( - cause, - self.engine_error_scope(), - self.context().execution_id(), - Some(message), + 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(), ) } } diff --git a/src/container_registry/scaleway_container_registry.rs b/src/container_registry/scaleway_container_registry.rs index ecaf109c..67aa86a6 100644 --- a/src/container_registry/scaleway_container_registry.rs +++ b/src/container_registry/scaleway_container_registry.rs @@ -8,7 +8,9 @@ use crate::container_registry::docker::{ docker_login, docker_manifest_inspect, docker_pull_image, docker_tag_and_push_image, }; use crate::container_registry::{ContainerRegistry, Kind, PullResult, PushResult}; -use crate::error::{EngineError, EngineErrorCause}; +use crate::errors::{CommandError, EngineError}; +use crate::events::{EngineEvent, EventMessage}; +use crate::logger::{LogLevel, Logger}; use crate::models::{ Context, Listen, Listener, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope, }; @@ -18,7 +20,7 @@ use retry::Error::Operation; use retry::OperationResult; use rusoto_core::param::ToParam; -pub struct ScalewayCR { +pub struct ScalewayCR<'a> { context: Context, id: String, name: String, @@ -27,9 +29,10 @@ pub struct ScalewayCR { secret_token: String, zone: ScwZone, listeners: Listeners, + logger: &'a dyn Logger, } -impl ScalewayCR { +impl<'a> ScalewayCR<'a> { pub fn new( context: Context, id: &str, @@ -37,6 +40,7 @@ impl ScalewayCR { secret_token: &str, default_project_id: &str, zone: ScwZone, + logger: &'a dyn Logger, ) -> ScalewayCR { ScalewayCR { context, @@ -47,6 +51,7 @@ impl ScalewayCR { secret_token: secret_token.to_string(), zone, listeners: Vec::new(), + logger, } } @@ -84,9 +89,15 @@ impl ScalewayCR { )) { Ok(res) => res.namespaces, Err(e) => { - error!( - "Error while interacting with Scaleway API (list_namespaces), error: {}, image: {}", - e, &image.name + self.logger.log( + LogLevel::Warning, + EngineEvent::Warning( + self.get_event_details(), + EventMessage::new( + "Error while interacting with Scaleway API (list_namespaces).".to_string(), + Some(format!("error: {}, image: {}", e, &image.name)), + ), + ), ); return None; } @@ -121,9 +132,15 @@ impl ScalewayCR { )) { Ok(res) => res.images, Err(e) => { - error!( - "Error while interacting with Scaleway API (list_images), error: {}, image: {}", - e, &image.name + self.logger.log( + LogLevel::Warning, + EngineEvent::Warning( + self.get_event_details(), + EventMessage::new( + "Error while interacting with Scaleway API (list_namespaces).".to_string(), + Some(format!("error: {}, image: {}", e, &image.name)), + ), + ), ); return None; } @@ -143,13 +160,20 @@ impl ScalewayCR { } pub fn delete_image(&self, image: &Image) -> Result { + let event_details = self.get_event_details(); + // https://developers.scaleway.com/en/products/registry/api/#delete-67dbf7 let image_to_delete = self.get_image(image); if image_to_delete.is_none() { - let message = format!("While tyring to delete image {}, image doesn't exist", &image.name,); - error!("{}", message); + let err = EngineError::new_container_registry_image_doesnt_exist( + event_details.clone(), + image.name.to_string(), + None, + ); - return Err(self.engine_error(EngineErrorCause::Internal, message)); + self.logger.log(LogLevel::Error, EngineEvent::Error(err.clone(), None)); + + return Err(err); } let image_to_delete = image_to_delete.unwrap(); @@ -161,49 +185,62 @@ impl ScalewayCR { )) { Ok(res) => Ok(res), Err(e) => { - let message = format!( - "Error while interacting with Scaleway API (delete_image), error: {}, image: {}", - e, &image.name + let err = EngineError::new_container_registry_delete_image_error( + event_details.clone(), + image.name.to_string(), + Some(CommandError::new(e.to_string(), None)), ); - error!("{}", message); - Err(self.engine_error(EngineErrorCause::Internal, message)) + self.logger.log(LogLevel::Error, EngineEvent::Error(err.clone(), None)); + + Err(err) } } } fn push_image(&self, dest: String, image: &Image) -> Result { // https://www.scaleway.com/en/docs/deploy-an-image-from-registry-to-kubernetes-kapsule/ + let event_details = self.get_event_details(); + match docker_tag_and_push_image( self.kind(), self.get_docker_envs(), image.name.clone(), image.tag.clone(), dest, + event_details.clone(), + self.logger(), ) { Ok(_) => {} - Err(e) => { - return Err(self.engine_error( - EngineErrorCause::Internal, - e.message - .unwrap_or_else(|| "unknown error occurring during docker push".to_string()), - )) - } - }; + Err(e) => Err(EngineError::new_docker_push_image_error( + event_details, + image.name.to_string(), + dest.to_string(), + e, + )), + } let result = retry::retry(Fibonacci::from_millis(10000).take(10), || { match self.does_image_exists(image) { true => OperationResult::Ok(&image), false => { - warn!("image is not yet available on Scaleway Registry Namespace, retrying in a few seconds..."); + self.logger.log( + LogLevel::Warning, + EngineEvent::Warning( + self.get_event_details(), + EventMessage::new_from_safe( + "Image is not yet available on Scaleway Registry Namespace, retrying in a few seconds...".to_string(), + ), + ), + ); OperationResult::Retry(()) } } }); - let image_not_reachable = Err(self.engine_error( - EngineErrorCause::Internal, - "image has been pushed on Scaleway Registry Namespace but is not yet available after 4min. Please try to redeploy in a few minutes".to_string(), + let image_not_reachable = Err(EngineError::new_container_registry_image_unreachable_after_push( + event_details.clone(), + image.name.to_string(), )); match result { @@ -214,15 +251,22 @@ impl ScalewayCR { } fn pull_image(&self, dest: String, image: &Image) -> Result { - match docker_pull_image(self.kind(), self.get_docker_envs(), dest) { + let event_details = self.get_event_details(); + + match docker_pull_image( + self.kind(), + self.get_docker_envs(), + dest, + event_details.clone(), + self.logger(), + ) { Ok(_) => {} - Err(e) => { - return Err(self.engine_error( - EngineErrorCause::Internal, - e.message - .unwrap_or_else(|| "unknown error occurring during docker pull".to_string()), - )) - } + Err(e) => Err(EngineError::new_docker_pull_image_error( + event_details, + image.name.to_string(), + dest.to_string(), + e, + )), }; Ok(PullResult::Some(image.clone())) @@ -246,13 +290,17 @@ impl ScalewayCR { )) { Ok(res) => Ok(res), Err(e) => { - let message = format!( - "Error while interacting with Scaleway API (create_namespace), error: {}, image: {}", - e, &image.name + let error = EngineError::new_container_registry_namespace_creation_error( + event_details.clone(), + repository_name.to_string(), + self.name_with_id(), + CommandError::new(e.to_string(), Some("Can't create SCW repository".to_string())), ); - error!("{}", message); - Err(self.engine_error(EngineErrorCause::Internal, message)) + self.logger + .log(LogLevel::Error, EngineEvent::Error(error.clone(), None)); + + Err(error) } } } @@ -262,15 +310,19 @@ impl ScalewayCR { image: &Image, ) -> Result { // https://developers.scaleway.com/en/products/registry/api/#delete-c1ac9b + let event_details = self.get_event_details(); let registry_to_delete = self.get_registry_namespace(image); if registry_to_delete.is_none() { - let message = format!( - "While tyring to delete registry namespace for image {}, registry namespace doesn't exist", - &image.name, + let error = EngineError::new_container_registry_repository_doesnt_exist( + event_details.clone(), + image.registry_name.unwrap_or("unknown".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 registry_to_delete = registry_to_delete.unwrap(); @@ -282,13 +334,16 @@ impl ScalewayCR { )) { Ok(res) => Ok(res), Err(e) => { - let message = format!( - "Error while interacting with Scaleway API (delete_namespace), error: {}, image: {}", - e, &image.name + let error = EngineError::new_container_registry_delete_repository_error( + event_details.clone(), + image.registry_name.unwrap_or("unknown".to_string()), + Some(CommandError::new(e.to_string(), None)), ); - error!("{}", message); - Err(self.engine_error(EngineErrorCause::Internal, message)) + self.logger + .log(LogLevel::Error, EngineEvent::Error(error.clone(), None)); + + return Err(error); } } } @@ -298,9 +353,16 @@ impl ScalewayCR { image: &Image, ) -> Result { // check if the repository already exists + let event_details = self.get_event_details(); let registry_namespace = self.get_registry_namespace(&image); if let Some(namespace) = registry_namespace { - info!("Scaleway registry namespace {} already exists", image.name.as_str()); + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(format!("SCW repository {} already exists", image.name.as_str())), + ), + ); return Ok(namespace); } @@ -319,21 +381,20 @@ impl ScalewayCR { } fn exec_docker_login(&self, registry_url: &String) -> Result<(), EngineError> { + let event_details = self.get_event_details(); if docker_login( Kind::ScalewayCr, self.get_docker_envs(), self.login.clone(), self.secret_token.clone(), registry_url.clone(), + event_details.clone(), + self.logger(), ) .is_err() { - return Err(self.engine_error( - EngineErrorCause::User( - "Your Scaleway 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 Scaleway {}", self.name_with_id()), + return Err(EngineError::new_client_invalid_cloud_provider_credentials( + event_details, )); }; @@ -379,6 +440,7 @@ impl ContainerRegistry for ScalewayCR { } fn does_image_exists(&self, image: &Image) -> bool { + let event_details = self.get_event_details(); let registry_url = image .registry_url .as_ref() @@ -391,6 +453,8 @@ impl ContainerRegistry for ScalewayCR { self.login.clone(), self.secret_token.clone(), registry_url.clone(), + event_details.clone(), + self.logger(), ) { return false; } @@ -401,11 +465,14 @@ impl ContainerRegistry for ScalewayCR { image.name.clone(), image.tag.clone(), registry_url, + event_details.clone(), + self.logger(), ) .is_some() } fn pull(&self, image: &Image) -> Result { + let event_details = self.get_event_details(); let listeners_helper = ListenersHelper::new(&self.listeners); let mut image = image.clone(); @@ -413,10 +480,17 @@ impl ContainerRegistry for ScalewayCR { match self.get_or_create_registry_namespace(&image) { Ok(registry) => { - info!( - "Scaleway registry namespace for {} has been created", - image.name.as_str() + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(format!( + "Scaleway registry namespace for {} has been created", + image.name.as_str() + )), + ), ); + image.registry_name = Some(image.name.clone()); // Note: Repository namespace should have the same name as the image name image.registry_url = registry.endpoint.clone(); image.registry_secret = Some(self.secret_token.clone()); @@ -424,18 +498,23 @@ impl ContainerRegistry for ScalewayCR { registry_url = registry.endpoint.unwrap_or_else(|| "undefined".to_string()); } Err(e) => { - error!( - "Scaleway registry namespace for {} cannot be created, error: {:?}", - image.name.as_str(), - e - ); + self.logger.log(LogLevel::Error, EngineEvent::Error(e.clone(), None)); return Err(e); } } if !self.does_image_exists(&image) { - let info_message = format!("image {:?} does not exist in SCR {} repository", image, self.name()); - info!("{}", info_message.as_str()); + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(format!( + "Image {:?} does not exist in SCR {} repository", + image, + self.name() + )), + ), + ); listeners_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Application { @@ -450,7 +529,14 @@ impl ContainerRegistry for ScalewayCR { } let info_message = format!("pull image {:?} from SCR {} repository", image, self.name()); - info!("{}", info_message.as_str()); + + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(info_message.to_string()), + ), + ); listeners_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Application { @@ -470,16 +556,13 @@ impl ContainerRegistry for ScalewayCR { } fn push(&self, image: &Image, force_push: bool) -> Result { + let event_details = self.get_event_details(); let mut image = image.clone(); let registry_url: String; let registry_name: String; match self.get_or_create_registry_namespace(&image) { Ok(registry) => { - info!( - "Scaleway registry namespace for {} has been created", - image.name.as_str() - ); image.registry_name = Some(image.name.clone()); // Note: Repository namespace should have the same name as the image name image.registry_url = registry.endpoint.clone(); image.registry_secret = Some(self.secret_token.clone()); @@ -488,11 +571,7 @@ impl ContainerRegistry for ScalewayCR { registry_name = registry.name.unwrap(); } Err(e) => { - error!( - "Scaleway registry namespace for {} cannot be created, error: {:?}", - image.name.as_str(), - e - ); + self.logger.log(LogLevel::Error, EngineEvent::Error(e.clone(), None)); return Err(e); } } @@ -510,7 +589,13 @@ impl ContainerRegistry for ScalewayCR { image, registry_name, ); - info!("{}", info_message.as_str()); + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(info_message.to_string()), + ), + ); listeners_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Application { @@ -530,7 +615,13 @@ impl ContainerRegistry for ScalewayCR { self.name() ); - info!("{}", info_message.as_str()); + self.logger.log( + LogLevel::Info, + EngineEvent::Info( + event_details.clone(), + EventMessage::new_from_safe(info_message.to_string()), + ), + ); listeners_helper.deployment_in_progress(ProgressInfo::new( ProgressScope::Application { diff --git a/src/dns_provider/cloudflare.rs b/src/dns_provider/cloudflare.rs index e93ba3cf..8b51e133 100644 --- a/src/dns_provider/cloudflare.rs +++ b/src/dns_provider/cloudflare.rs @@ -1,7 +1,7 @@ use std::net::Ipv4Addr; use crate::dns_provider::{DnsProvider, Kind}; -use crate::error::{EngineError, EngineErrorCause}; +use crate::errors::EngineError; use crate::models::{Context, Domain}; pub struct Cloudflare { diff --git a/src/dns_provider/mod.rs b/src/dns_provider/mod.rs index 859199d2..62a62876 100644 --- a/src/dns_provider/mod.rs +++ b/src/dns_provider/mod.rs @@ -1,8 +1,8 @@ use std::net::Ipv4Addr; +use crate::errors::EngineError; use serde::{Deserialize, Serialize}; -use crate::error::{EngineError, EngineErrorCause, EngineErrorScope}; use crate::models::{Context, Domain}; pub mod cloudflare; @@ -21,17 +21,6 @@ pub trait DnsProvider { fn domain(&self) -> &Domain; fn resolvers(&self) -> Vec; fn is_valid(&self) -> Result<(), EngineError>; - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::DnsProvider(self.id().to_string(), self.name().to_string()) - } - fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError { - EngineError::new( - cause, - self.engine_error_scope(), - self.context().execution_id(), - Some(message), - ) - } } #[derive(Serialize, Deserialize, Clone, Debug)] diff --git a/src/engine.rs b/src/engine.rs index 28e3c43d..951c1d57 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 9401fabf..dbba9f7c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -68,6 +68,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/mod.rs b/src/errors/mod.rs index d165a552..6325864f 100644 --- a/src/errors/mod.rs +++ b/src/errors/mod.rs @@ -204,6 +204,46 @@ pub enum Tag { 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, + /// 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, + /// ContainerRegistryRepositoryCreationError: represents an error when trying to create a repository. + ContainerRegistryRepositoryCreationError, + /// ContainerRegistryRepositorySetLifecycleError: represents an error when trying to set repository lifecycle policy. + ContainerRegistryRepositorySetLifecycleError, + /// ContainerRegistryGetCredentialsError: represents an error when trying to get container registry credentials. + ContainerRegistryGetCredentialsError, + /// ContainerRegistryDeleteImageError: represents an error while trying to delete an image. + ContainerRegistryDeleteImageError, + /// ContainerRegistryImageDoesntExist: represents an error, image doesn't exist in the registry. + ContainerRegistryImageDoesntExist, + /// ContainerRegistryImageUnreachableAfterPush: represents an error when image has been pushed but is unreachable. + ContainerRegistryImageUnreachableAfterPush, + /// ContainerRegistryRepositoryDoesntExist: represents an error, repository doesn't exist. + ContainerRegistryRepositoryDoesntExist, + /// ContainerRegistryDeleteRepositoryError: represents an error while trying to delete a repository. + ContainerRegistryDeleteRepositoryError, + /// NotImplementedError: represents an error where feature / code has not been implemented yet. + NotImplementedError, } #[derive(Clone, Debug)] @@ -1852,4 +1892,512 @@ impl EngineError { 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 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()), + ) + } + + /// 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 create a new container registry namespace. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `repository_name`: Container repository name. + /// * `registry_name`: Registry to be created. + /// * `raw_error`: Raw error message. + pub fn new_container_registry_namespace_creation_error( + event_details: EventDetails, + repository_name: String, + registry_name: String, + raw_error: CommandError, + ) -> EngineError { + let message = format!( + "Error, trying to create registry `{}` in `{}`.", + registry_name, repository_name + ); + + EngineError::new( + event_details, + Tag::ContainerRegistryRepositoryCreationError, + message.to_string(), + message.to_string(), + Some(raw_error), + None, + None, + ) + } + + /// Creates new error when trying to set container repository lifecycle policy. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `repository_name`: Repository name. + /// * `raw_error`: Raw error message. + pub fn new_container_registry_repository_set_lifecycle_policy_error( + event_details: EventDetails, + repository_name: String, + raw_error: CommandError, + ) -> EngineError { + let message = format!( + "Error, trying to set lifecycle policy repository `{}`.", + repository_name, + ); + + EngineError::new( + event_details, + Tag::ContainerRegistryRepositorySetLifecycleError, + message.to_string(), + message.to_string(), + Some(raw_error), + None, + None, + ) + } + + /// Creates new error when trying to get container registry credentials. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `repository_name`: Repository name. + pub fn new_container_registry_get_credentials_error( + event_details: EventDetails, + repository_name: String, + ) -> EngineError { + let message = format!( + "Failed to retrieve credentials and endpoint URL from container registry `{}`.", + repository_name, + ); + + EngineError::new( + event_details, + Tag::ContainerRegistryGetCredentialsError, + message.to_string(), + message.to_string(), + None, + None, + None, + ) + } + + /// Creates new error when trying to delete an image. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `image_name`: Image name. + /// * `raw_error`: Raw error message. + pub fn new_container_registry_delete_image_error( + event_details: EventDetails, + image_name: String, + raw_error: Option, + ) -> EngineError { + let message = format!("Failed to delete image `{}`.", image_name,); + + EngineError::new( + event_details, + Tag::ContainerRegistryDeleteImageError, + message.to_string(), + message.to_string(), + raw_error, + None, + None, + ) + } + + /// Creates new error when trying to get image from a registry. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `image_name`: Image name. + pub fn new_container_registry_image_doesnt_exist( + event_details: EventDetails, + image_name: String, + raw_error: Option, + ) -> EngineError { + let message = format!("Image `{}` doesn't exists.", image_name,); + + EngineError::new( + event_details, + Tag::ContainerRegistryImageDoesntExist, + message.to_string(), + message.to_string(), + raw_error, + None, + None, + ) + } + + /// Creates new error when image is unreachable after push. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `image_name`: Image name. + /// * `raw_error`: Raw error message. + pub fn new_container_registry_image_unreachable_after_push( + event_details: EventDetails, + image_name: String, + ) -> EngineError { + let message = format!( + "Image `{}` has been pushed on registry namespace but is not yet available after some time.", + image_name, + ); + + EngineError::new( + event_details, + Tag::ContainerRegistryImageUnreachableAfterPush, + message.to_string(), + message.to_string(), + None, + None, + Some("Please try to redeploy in a few minutes.".to_string()), + ) + } + + /// Creates new error when trying to get image from a registry. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `repository_name`: Repository name. + pub fn new_container_registry_repository_doesnt_exist( + event_details: EventDetails, + repository_name: String, + raw_error: Option, + ) -> EngineError { + let message = format!("Repository `{}` doesn't exists.", repository_name,); + + EngineError::new( + event_details, + Tag::ContainerRegistryRepositoryDoesntExist, + message.to_string(), + message.to_string(), + raw_error, + None, + None, + ) + } + + /// Creates new error when trying to delete repository. + /// + /// Arguments: + /// + /// * `event_details`: Error linked event details. + /// * `repository_name`: Repository name. + /// * `raw_error`: Raw error message. + pub fn new_container_registry_delete_repository_error( + event_details: EventDetails, + repository_name: String, + raw_error: Option, + ) -> EngineError { + let message = format!("Failed to delete repository `{}`.", repository_name,); + + EngineError::new( + event_details, + Tag::ContainerRegistryDeleteRepositoryError, + message.to_string(), + message.to_string(), + raw_error, + None, + None, + ) + } } diff --git a/src/events/mod.rs b/src/events/mod.rs index 4bbf96eb..e56b29c0 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}; @@ -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. @@ -449,7 +455,8 @@ mod tests { use crate::cloud_provider::Kind::Aws; use crate::errors::{CommandError, EngineError}; use crate::events::{ - EngineEvent, EnvironmentStep, EventDetails, EventMessage, InfrastructureStep, Stage, Transmitter, + EngineEvent, EnvironmentStep, EventDetails, EventMessage, EventMessageVerbosity, InfrastructureStep, Stage, + Transmitter, }; use crate::models::QoveryIdentifier; diff --git a/src/object_storage/mod.rs b/src/object_storage/mod.rs index 329c6ae4..e8d315f3 100644 --- a/src/object_storage/mod.rs +++ b/src/object_storage/mod.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::error::{EngineError, EngineErrorCause, EngineErrorScope}; +use crate::errors::EngineError; use crate::models::{Context, StringPath}; use std::fs::File; @@ -21,17 +21,6 @@ pub trait ObjectStorage { fn delete_bucket(&self, bucket_name: &str) -> Result<(), EngineError>; fn get(&self, bucket_name: &str, object_key: &str, use_cache: bool) -> Result<(StringPath, File), EngineError>; fn put(&self, bucket_name: &str, object_key: &str, file_path: &str) -> Result<(), EngineError>; - fn engine_error_scope(&self) -> EngineErrorScope { - EngineErrorScope::ObjectStorage(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), - ) - } } #[derive(Serialize, Deserialize, Clone)] diff --git a/src/object_storage/s3.rs b/src/object_storage/s3.rs index 68dbf09e..1d227c6f 100644 --- a/src/object_storage/s3.rs +++ b/src/object_storage/s3.rs @@ -5,6 +5,7 @@ use std::path::Path; use std::str::FromStr; use crate::cloud_provider::aws::regions::AwsRegion; +use crate::errors::EngineError; use rusoto_core::credential::StaticProvider; use rusoto_core::{Client, HttpClient, Region as RusotoRegion}; use rusoto_s3::{ @@ -14,7 +15,6 @@ use rusoto_s3::{ }; use tokio::io; -use crate::error::{EngineError, EngineErrorCause}; use crate::models::{Context, StringPath}; use crate::object_storage::{Kind, ObjectStorage}; use crate::runtime::block_on; diff --git a/src/object_storage/scaleway_object_storage.rs b/src/object_storage/scaleway_object_storage.rs index 07982e9b..3b172c7c 100644 --- a/src/object_storage/scaleway_object_storage.rs +++ b/src/object_storage/scaleway_object_storage.rs @@ -3,10 +3,10 @@ use std::fs::File; use std::path::Path; use crate::cloud_provider::scaleway::application::ScwZone; -use crate::error::{EngineError, EngineErrorCause}; use crate::models::{Context, StringPath}; use crate::object_storage::{Kind, ObjectStorage}; +use crate::errors::EngineError; use crate::runtime::block_on; use rusoto_core::{Client, HttpClient, Region as RusotoRegion}; use rusoto_credential::StaticProvider; diff --git a/src/object_storage/spaces.rs b/src/object_storage/spaces.rs index 13f1ee92..e0bd96aa 100644 --- a/src/object_storage/spaces.rs +++ b/src/object_storage/spaces.rs @@ -12,7 +12,7 @@ use rusoto_s3::{ use tokio::io; use crate::cloud_provider::digitalocean::application::DoRegion; -use crate::error::{EngineError, EngineErrorCause}; +use crate::errors::EngineError; use crate::models::{Context, StringPath}; use crate::object_storage::{Kind, ObjectStorage}; use crate::runtime; diff --git a/src/transaction.rs b/src/transaction.rs index 29ae17a9..07c4512e 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -6,8 +6,7 @@ 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; use crate::models::{ Action, Environment, EnvironmentAction, EnvironmentError, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope, @@ -28,7 +27,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 +37,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 +47,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));