chore: simplify error handling

chore: improve log messages for the user
chore: make clear differentiation between internal and user errors
This commit is contained in:
Romaric Philogene
2020-11-01 01:43:44 +01:00
parent b95be0a016
commit 9b718e7491
38 changed files with 1981 additions and 1347 deletions

View File

@@ -1,14 +1,14 @@
use chrono::Utc;
use qovery_engine::build_platform::local_docker::LocalDocker;
use qovery_engine::cloud_provider::aws::AWS;
use qovery_engine::cloud_provider::aws::kubernetes::{EKS, Options};
use qovery_engine::cloud_provider::aws::kubernetes::node::Node;
use qovery_engine::cloud_provider::aws::kubernetes::{Options, EKS};
use qovery_engine::cloud_provider::aws::AWS;
use qovery_engine::cloud_provider::TerraformStateCredentials;
use qovery_engine::container_registry::ecr::ECR;
use qovery_engine::dns_provider::cloudflare::Cloudflare;
use qovery_engine::engine::Engine;
use qovery_engine::error::ConfigurationError;
use qovery_engine::error::EngineError;
use qovery_engine::models::{
Action, Application, Context, Environment, EnvironmentAction, GitCredentials, Kind,
};
@@ -82,10 +82,10 @@ fn main() {
let session = match engine.session() {
Ok(session) => session,
Err(config_error) => match config_error {
ConfigurationError::BuildPlatform(_) => panic!("build platform config error"),
ConfigurationError::ContainerRegistry(_) => panic!("container registry config error"),
ConfigurationError::CloudProvider(_) => panic!("cloud provider config error"),
ConfigurationError::DnsProvider(_) => panic!("dns provider config error"),
EngineError::BuildPlatform(_) => panic!("build platform config error"),
EngineError::ContainerRegistry(_) => panic!("container registry config error"),
EngineError::CloudProvider(_) => panic!("cloud provider config error"),
EngineError::DnsProvider(_) => panic!("dns provider config error"),
},
};

View File

@@ -1,15 +1,15 @@
use chrono::Utc;
use qovery_engine::build_platform::local_docker::LocalDocker;
use qovery_engine::cloud_provider::aws::AWS;
use qovery_engine::cloud_provider::aws::kubernetes::{EKS, Options};
use qovery_engine::cloud_provider::aws::kubernetes::node::Node;
use qovery_engine::cloud_provider::aws::kubernetes::{Options, EKS};
use qovery_engine::cloud_provider::aws::router::Router;
use qovery_engine::cloud_provider::aws::AWS;
use qovery_engine::cloud_provider::TerraformStateCredentials;
use qovery_engine::container_registry::ecr::ECR;
use qovery_engine::dns_provider::cloudflare::Cloudflare;
use qovery_engine::engine::Engine;
use qovery_engine::error::ConfigurationError;
use qovery_engine::error::EngineError;
use qovery_engine::models::{
Action, Application, Context, Environment, EnvironmentAction, GitCredentials, Kind,
};
@@ -83,10 +83,10 @@ fn main() {
let session = match engine.session() {
Ok(session) => session,
Err(config_error) => match config_error {
ConfigurationError::BuildPlatform(_) => panic!("build platform config error"),
ConfigurationError::ContainerRegistry(_) => panic!("container registry config error"),
ConfigurationError::CloudProvider(_) => panic!("cloud provider config error"),
ConfigurationError::DnsProvider(_) => panic!("dns provider config error"),
EngineError::BuildPlatform(_) => panic!("build platform config error"),
EngineError::ContainerRegistry(_) => panic!("container registry config error"),
EngineError::CloudProvider(_) => panic!("cloud provider config error"),
EngineError::DnsProvider(_) => panic!("dns provider config error"),
},
};

View File

@@ -1,4 +0,0 @@
#[derive(Debug)]
pub enum BuildPlatformError {
Unexpected(String),
}

View File

@@ -1,15 +1,16 @@
use std::path::Path;
use std::rc::Rc;
use crate::build_platform::error::BuildPlatformError;
use crate::build_platform::{Build, BuildError, BuildPlatform, BuildResult, Image, Kind};
use git2::Error;
use crate::build_platform::{Build, BuildPlatform, BuildResult, Image, Kind};
use crate::error::{EngineError, EngineErrorCause, EngineErrorScope};
use crate::fs::workspace_directory;
use crate::git::checkout_submodules;
use crate::models::{
Context, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressListener,
ProgressScope,
};
use crate::transaction::CommitError::BuildImage;
use crate::{cmd, git};
/// use Docker in local
@@ -30,7 +31,7 @@ impl LocalDocker {
}
}
fn image_does_exist(&self, image: &Image) -> Result<bool, BuildError> {
fn image_does_exist(&self, image: &Image) -> Result<bool, EngineError> {
let envs = match self.context.docker_tcp_socket() {
Some(tcp_socket) => vec![("DOCKER_HOST", tcp_socket.as_str())],
None => vec![],
@@ -66,10 +67,11 @@ impl BuildPlatform for LocalDocker {
self.name.as_str()
}
fn is_valid(&self) -> Result<(), BuildPlatformError> {
fn is_valid(&self) -> Result<(), EngineError> {
if !crate::cmd::utilities::does_binary_exist("docker") {
return Err(BuildPlatformError::Unexpected(
"docker binary not found".to_string(),
return Err(self.engine_error(
EngineErrorCause::Internal,
String::from("docker binary not found"),
));
}
@@ -80,7 +82,7 @@ impl BuildPlatform for LocalDocker {
self.listeners.push(listener);
}
fn build(&self, build: Build, force_build: bool) -> Result<BuildResult, BuildError> {
fn build(&self, build: Build, force_build: bool) -> Result<BuildResult, EngineError> {
info!("LocalDocker.build() called for {}", self.name());
let listeners_helper = ListenersHelper::new(&self.listeners);
@@ -111,8 +113,14 @@ impl BuildPlatform for LocalDocker {
match git_clone {
Ok(_) => {}
Err(err) => {
error! {"Error while trying to clone repository {}", build.git_repository.url}
return Err(BuildError::Git(err));
let message = format!(
"Error while cloning repository {}. Error: {:?}",
&build.git_repository.url, err
);
error!("{}", message);
return Err(self.engine_error(EngineErrorCause::Internal, message));
}
}
@@ -121,12 +129,32 @@ impl BuildPlatform for LocalDocker {
let commit_id = &build.git_repository.commit_id;
match git::checkout(&repo, &commit_id, build.git_repository.url.as_str()) {
Ok(_) => {}
Err(err) => return Err(BuildError::Git(err)),
Err(err) => {
let message = format!(
"Error while git checkout repository {} with commit id {}. Error: {:?}",
&build.git_repository.url, commit_id, err
);
error!("{}", message);
return Err(self.engine_error(EngineErrorCause::Internal, message));
}
}
// git checkout submodules
let _ = checkout_submodules(&repo);
// TODO what if we can't checkout submodules? Today we ignore it
match checkout_submodules(repo) {
Ok(_) => {}
Err(err) => {
let message = format!(
"Error while checkout submodules from repository {}. Error: {:?}",
&build.git_repository.url, err
);
error!("{}", message);
return Err(self.engine_error(EngineErrorCause::Internal, message));
}
}
let into_dir_docker_style = format!("{}/.", into_dir.as_str());
@@ -140,11 +168,14 @@ impl BuildPlatform for LocalDocker {
match Path::new(dockerfile_complete_path.as_str()).exists() {
false => {
error!(
let message = format!(
"Unable to find Dockerfile path {}",
dockerfile_complete_path.as_str()
);
return Err(BuildError::Error);
error!("{}", &message);
return Err(self.engine_error(EngineErrorCause::Internal, message));
}
_ => {}
}
@@ -220,7 +251,19 @@ impl BuildPlatform for LocalDocker {
match exit_status {
Ok(_) => {}
Err(_) => return Err(BuildError::Error),
Err(err) => {
return Err(self.engine_error(
EngineErrorCause::User(
"It looks like your Dockerfile is wrong. Did you consider building \
your container locally using `qovery run` or `docker build`?",
),
format!(
"error while building container image {}. Error: {:?}",
self.name_with_id(),
err
),
));
}
}
listeners_helper.start_in_progress(ProgressInfo::new(
@@ -228,27 +271,34 @@ impl BuildPlatform for LocalDocker {
id: build.image.application_id.clone(),
},
ProgressLevel::Info,
Some("build is done ✔"),
Some(format!(
"container build is done for {}",
self.name_with_id()
)),
self.context.execution_id(),
));
Ok(BuildResult { build })
}
fn build_error(&self, build: Build) -> Result<BuildResult, BuildError> {
fn build_error(&self, build: Build) -> Result<BuildResult, EngineError> {
warn!("LocalDocker.build_error() called for {}", self.name());
let listener_helper = ListenersHelper::new(&self.listeners);
// FIXME
let message = String::from("something goes wrong (not implemented)");
listener_helper.error(ProgressInfo::new(
ProgressScope::Application {
id: build.image.application_id,
},
ProgressLevel::Error,
Some("something goes wrong (not implemented)"),
Some(message.as_str()),
self.context.execution_id(),
));
// FIXME
Err(BuildError::Error)
Err(self.engine_error(EngineErrorCause::Internal, message))
}
}

View File

@@ -3,11 +3,10 @@ use std::rc::Rc;
use git2::Error;
use serde::{Deserialize, Serialize};
use crate::build_platform::error::BuildPlatformError;
use crate::error::{EngineError, EngineErrorCause, EngineErrorScope};
use crate::git::Credentials;
use crate::models::{Context, ProgressListener};
pub mod error;
pub mod local_docker;
pub trait BuildPlatform {
@@ -15,10 +14,24 @@ pub trait BuildPlatform {
fn kind(&self) -> Kind;
fn id(&self) -> &str;
fn name(&self) -> &str;
fn is_valid(&self) -> Result<(), BuildPlatformError>;
fn name_with_id(&self) -> String {
format!("{} ({})", self.name(), self.id())
}
fn is_valid(&self) -> Result<(), EngineError>;
fn add_listener(&mut self, listener: Rc<Box<dyn ProgressListener>>);
fn build(&self, build: Build, force_build: bool) -> Result<BuildResult, BuildError>;
fn build_error(&self, build: Build) -> Result<BuildResult, BuildError>;
fn build(&self, build: Build, force_build: bool) -> Result<BuildResult, EngineError>;
fn build_error(&self, build: Build) -> Result<BuildResult, EngineError>;
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::BuildPlatform(self.id().to_string(), self.name().to_string())
}
fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError {
EngineError::new(
cause,
self.engine_error_scope(),
self.context().execution_id(),
Some(message),
)
}
}
pub struct Build {
@@ -62,12 +75,6 @@ pub struct BuildResult {
pub build: Build,
}
#[derive(Debug)]
pub enum BuildError {
Git(Error),
Error,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Kind {

View File

@@ -6,11 +6,14 @@ use crate::cloud_provider::aws::{common, AWS};
use crate::cloud_provider::environment::Environment;
use crate::cloud_provider::kubernetes::Kubernetes;
use crate::cloud_provider::service::{
Action, Application as CApplication, Create, Delete, Pause, Service, ServiceError, ServiceType,
Action, Application as CApplication, Create, Delete, Pause, Service, ServiceType,
StatelessService,
};
use crate::cloud_provider::DeploymentTarget;
use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY};
use crate::error::{
from_simple_error_to_engine_error, EngineError, EngineErrorCause, EngineErrorScope,
};
use crate::models::Context;
#[derive(Clone, Eq, PartialEq, Hash)]
@@ -126,7 +129,7 @@ impl Application {
context
}
fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), ServiceError> {
fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), EngineError> {
let (kubernetes, environment) = match target {
DeploymentTarget::ManagedServices(k, env) => (*k, *env),
DeploymentTarget::SelfHosted(k, env) => (*k, *env),
@@ -137,20 +140,28 @@ impl Application {
let selector = format!("app={}", self.name());
if is_error {
let _ = common::get_stateless_resource_information(
kubernetes,
environment,
workspace_dir.as_str(),
selector.as_str(),
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
common::get_stateless_resource_information(
kubernetes,
environment,
workspace_dir.as_str(),
selector.as_str(),
),
)?;
}
// clean the resource
let _ = common::do_stateless_service_cleanup(
kubernetes,
environment,
workspace_dir.as_str(),
helm_release_name.as_str(),
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
common::do_stateless_service_cleanup(
kubernetes,
environment,
workspace_dir.as_str(),
helm_release_name.as_str(),
),
)?;
Ok(())
@@ -212,7 +223,7 @@ impl Service for Application {
}
impl Create for Application {
fn on_create(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
info!("AWS.application.on_create() called for {}", self.name());
let (kubernetes, environment) = match target {
DeploymentTarget::ManagedServices(k, env) => (*k, *env),
@@ -229,10 +240,15 @@ impl Create for Application {
let workspace_dir = self.workspace_directory();
let from_dir = format!("{}/aws/charts/q-application", self.context.lib_root_dir());
let _ = crate::template::generate_and_copy_all_files_into_dir(
from_dir.as_str(),
workspace_dir.as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
from_dir.as_str(),
workspace_dir.as_str(),
&context,
),
)?;
// render
@@ -243,51 +259,67 @@ impl Create for Application {
(AWS_SECRET_ACCESS_KEY, aws.secret_access_key.as_str()),
];
let kubernetes_config_file_path = common::kubernetes_config_path(
workspace_dir.as_str(),
environment.organization_id.as_str(),
kubernetes.id(),
aws.access_key_id.as_str(),
aws.secret_access_key.as_str(),
kubernetes.region(),
let kubernetes_config_file_path = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
common::kubernetes_config_path(
workspace_dir.as_str(),
environment.organization_id.as_str(),
kubernetes.id(),
aws.access_key_id.as_str(),
aws.secret_access_key.as_str(),
kubernetes.region(),
),
)?;
// do exec helm upgrade and return the last deployment status
let helm_history_row = crate::cmd::helm::helm_exec_with_upgrade_history(
kubernetes_config_file_path.as_str(),
environment.namespace(),
helm_release_name.as_str(),
workspace_dir.as_str(),
aws_credentials_envs.clone(),
let helm_history_row = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::cmd::helm::helm_exec_with_upgrade_history(
kubernetes_config_file_path.as_str(),
environment.namespace(),
helm_release_name.as_str(),
workspace_dir.as_str(),
aws_credentials_envs.clone(),
),
)?;
// check deployment status
if helm_history_row.is_none() || !helm_history_row.unwrap().is_successfully_deployed() {
// TODO get pod output by using kubectl and return it into the OnCreateFailed
return Err(ServiceError::OnCreateFailed);
return Err(self.engine_error(
EngineErrorCause::User(
"Your application didn't start for some reason. \
Are you sure your application is correctly running? You can give a try by running \
locally `qovery run`. You can also check the application log from the web \
interface or the CLI with `qovery log`",
),
format!("Application {} has failed to start ", self.name_with_id()),
));
}
// check app status
let selector = format!("app={}", self.name());
match crate::cmd::kubectl::kubectl_exec_is_pod_ready_with_retry(
kubernetes_config_file_path.as_str(),
environment.namespace(),
selector.as_str(),
aws_credentials_envs,
) {
Ok(Some(true)) => {}
_ => return Err(ServiceError::OnCreateFailed),
}
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::cmd::kubectl::kubectl_exec_is_pod_ready_with_retry(
kubernetes_config_file_path.as_str(),
environment.namespace(),
selector.as_str(),
aws_credentials_envs,
),
)?;
Ok(())
}
fn on_create_check(&self) -> Result<(), ServiceError> {
fn on_create_check(&self) -> Result<(), EngineError> {
Ok(())
}
fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
warn!(
"AWS.application.on_create_error() called for {}",
self.name()
@@ -311,29 +343,42 @@ impl Create for Application {
(AWS_SECRET_ACCESS_KEY, aws.secret_access_key.as_str()),
];
let kubernetes_config_file_path = common::kubernetes_config_path(
workspace_dir.as_str(),
environment.organization_id.as_str(),
kubernetes.id(),
aws.access_key_id.as_str(),
aws.secret_access_key.as_str(),
kubernetes.region(),
let kubernetes_config_file_path = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
common::kubernetes_config_path(
workspace_dir.as_str(),
environment.organization_id.as_str(),
kubernetes.id(),
aws.access_key_id.as_str(),
aws.secret_access_key.as_str(),
kubernetes.region(),
),
)?;
let helm_release_name = self.helm_release_name();
let history_rows = crate::cmd::helm::helm_exec_history(
kubernetes_config_file_path.as_str(),
environment.namespace(),
helm_release_name.as_str(),
aws_credentials_envs.clone(),
)?;
if history_rows.len() == 1 {
crate::cmd::helm::helm_exec_uninstall(
let history_rows = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::cmd::helm::helm_exec_history(
kubernetes_config_file_path.as_str(),
environment.namespace(),
helm_release_name.as_str(),
aws_credentials_envs.clone(),
),
)?;
if history_rows.len() == 1 {
from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::cmd::helm::helm_exec_uninstall(
kubernetes_config_file_path.as_str(),
environment.namespace(),
helm_release_name.as_str(),
aws_credentials_envs.clone(),
),
)?;
}
Ok(())
@@ -341,16 +386,16 @@ impl Create for Application {
}
impl Pause for Application {
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
info!("AWS.application.on_pause() called for {}", self.name());
self.delete(target, false)
}
fn on_pause_check(&self) -> Result<(), ServiceError> {
fn on_pause_check(&self) -> Result<(), EngineError> {
Ok(())
}
fn on_pause_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_pause_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
warn!(
"AWS.application.on_pause_error() called for {}",
self.name()
@@ -360,16 +405,16 @@ impl Pause for Application {
}
impl Delete for Application {
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
info!("AWS.application.on_delete() called for {}", self.name());
self.delete(target, false)
}
fn on_delete_check(&self) -> Result<(), ServiceError> {
fn on_delete_check(&self) -> Result<(), EngineError> {
Ok(())
}
fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
warn!(
"AWS.application.on_delete_error() called for {}",
self.name()

View File

@@ -6,9 +6,11 @@ use rusoto_core::Region;
use crate::cloud_provider::aws::AWS;
use crate::cloud_provider::environment::Environment;
use crate::cloud_provider::kubernetes::Kubernetes;
use crate::cloud_provider::service::ServiceError;
use crate::cmd::utilities::CmdError;
use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY};
use crate::error::{EngineError, SimpleError};
pub type Logs = String;
pub type Describe = String;
pub fn kubernetes_config_path(
workspace_directory: &str,
@@ -17,7 +19,7 @@ pub fn kubernetes_config_path(
access_key_id: &str,
secret_access_key: &str,
region: &str,
) -> Result<String, Error> {
) -> Result<String, SimpleError> {
let kubernetes_config_bucket_name = format!("qovery-kubeconfigs-{}", kubernetes_cluster_id);
let kubernetes_config_object_key = format!("{}.yaml", kubernetes_cluster_id);
@@ -40,16 +42,13 @@ pub fn kubernetes_config_path(
Ok(kubernetes_config_file_path)
}
pub type Logs = String;
pub type Describe = String;
/// show different output (kubectl describe, log..) for debug purpose
pub fn get_stateless_resource_information(
kubernetes: &dyn Kubernetes,
environment: &Environment,
workspace_dir: &str,
selector: &str,
) -> Result<(Describe, Logs), CmdError> {
) -> Result<(Describe, Logs), SimpleError> {
let aws = kubernetes
.cloud_provider()
.as_any()
@@ -112,7 +111,7 @@ pub fn do_stateless_service_cleanup(
environment: &Environment,
workspace_dir: &str,
helm_release_name: &str,
) -> Result<(), ServiceError> {
) -> Result<(), SimpleError> {
let aws = kubernetes
.cloud_provider()
.as_any()

View File

@@ -6,10 +6,13 @@ use crate::cloud_provider::environment::Environment;
use crate::cloud_provider::kubernetes::Kubernetes;
use crate::cloud_provider::service::{
Action, Backup, Create, Database, DatabaseOptions, DatabaseType, Delete, Downgrade, Pause,
Service, ServiceError, ServiceType, StatefulService, Upgrade,
Service, ServiceType, StatefulService, Upgrade,
};
use crate::cloud_provider::DeploymentTarget;
use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY};
use crate::error::{
from_simple_error_to_engine_error, EngineError, EngineErrorCause, EngineErrorScope,
};
use crate::models::Context;
pub struct MongoDB {
@@ -79,12 +82,14 @@ impl MongoDB {
.as_any()
.downcast_ref::<AWS>()
.expect("Could not downcast kubernetes.cloud_provider() to AWS");
// we need the kubernetes config file to store tfstates file in kube secrets
let kubernetes_config_file_path = utilities::get_kubernetes_config_path(
self.workspace_directory().as_str(),
kubernetes,
environment,
);
match kubernetes_config_file_path {
Ok(kube_config) => {
context.insert("kubeconfig_path", &kube_config.as_str());
@@ -96,8 +101,12 @@ impl MongoDB {
utilities::create_namespace(&environment.namespace(), kube_config.as_str(), aws);
}
Err(e) => error!("Failed to generate the kubernetes config file path: {}", e),
Err(e) => error!(
"Failed to generate the kubernetes config file path: {:?}",
e
),
}
context.insert("namespace", environment.namespace());
context.insert("aws_access_key", &cp.access_key_id);
@@ -122,7 +131,7 @@ impl MongoDB {
context
}
fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), ServiceError> {
fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), EngineError> {
let workspace_dir = self.workspace_directory();
match target {
@@ -134,39 +143,58 @@ impl MongoDB {
let context = self.tera_context(*kubernetes, *environment);
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(),
&workspace_dir,
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(),
&workspace_dir,
&context,
),
)?;
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/mongodb", self.context.lib_root_dir()).as_str(),
workspace_dir.as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/mongodb", self.context.lib_root_dir()).as_str(),
workspace_dir.as_str(),
&context,
),
)?;
crate::template::generate_and_copy_all_files_into_dir(
format!(
"{}/aws/charts/external-name-svc",
self.context.lib_root_dir()
)
.as_str(),
format!("{}/{}", workspace_dir, "external-name-svc").as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!(
"{}/aws/charts/external-name-svc",
self.context.lib_root_dir()
)
.as_str(),
format!("{}/{}", workspace_dir, "external-name-svc").as_str(),
&context,
),
)?;
crate::template::generate_and_copy_all_files_into_dir(
format!(
"{}/aws/charts/external-name-svc",
self.context.lib_root_dir()
)
.as_str(),
workspace_dir.as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!(
"{}/aws/charts/external-name-svc",
self.context.lib_root_dir()
)
.as_str(),
workspace_dir.as_str(),
&context,
),
)?;
match crate::cmd::terraform::terraform_exec_with_init_validate_destroy(
workspace_dir.as_str(),
) {
Ok(o) => {
Ok(_) => {
info!("Deleting secrets containing tfstates");
utilities::delete_terraform_tfstate_secret(
*kubernetes,
@@ -174,8 +202,16 @@ impl MongoDB {
self.workspace_directory().as_str(),
);
}
//TODO: find a way to raise the error
Err(e) => error!("Error while destroying infrastructure {}", e),
Err(e) => {
let message = format!(
"Error while destroying infrastructure {}",
e.message.unwrap_or("".into())
);
error!("{}", message);
return Err(self.engine_error(EngineErrorCause::Internal, message));
}
}
}
DeploymentTarget::SelfHosted(kubernetes, environment) => {
@@ -183,20 +219,28 @@ impl MongoDB {
let selector = format!("app={}", self.name());
if is_error {
let _ = common::get_stateless_resource_information(
*kubernetes,
*environment,
workspace_dir.as_str(),
selector.as_str(),
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
common::get_stateless_resource_information(
*kubernetes,
*environment,
workspace_dir.as_str(),
selector.as_str(),
),
)?;
}
// clean the resource
let _ = common::do_stateless_service_cleanup(
*kubernetes,
*environment,
workspace_dir.as_str(),
helm_release_name.as_str(),
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
common::do_stateless_service_cleanup(
*kubernetes,
*environment,
workspace_dir.as_str(),
helm_release_name.as_str(),
),
)?;
}
}
@@ -252,7 +296,7 @@ impl Service for MongoDB {
impl Database for MongoDB {}
impl Create for MongoDB {
fn on_create(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
info!("AWS.MongoDB.on_create() called for {}", self.name());
let workspace_dir = self.workspace_directory();
@@ -264,30 +308,48 @@ impl Create for MongoDB {
let context = self.tera_context(*kubernetes, *environment);
let workspace_dir = self.workspace_directory();
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(),
&workspace_dir,
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(),
&workspace_dir,
&context,
),
)?;
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/mongodb", self.context.lib_root_dir()).as_str(),
workspace_dir.as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/mongodb", self.context.lib_root_dir()).as_str(),
workspace_dir.as_str(),
&context,
),
)?;
crate::template::generate_and_copy_all_files_into_dir(
format!(
"{}/aws/charts/external-name-svc",
self.context.lib_root_dir()
)
.as_str(),
format!("{}/{}", workspace_dir, "external-name-svc").as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!(
"{}/aws/charts/external-name-svc",
self.context.lib_root_dir()
)
.as_str(),
format!("{}/{}", workspace_dir, "external-name-svc").as_str(),
&context,
),
)?;
// deploy database + external DNS
crate::cmd::terraform::terraform_exec_with_init_validate_plan_apply(
workspace_dir.as_str(),
false,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::cmd::terraform::terraform_exec_with_init_validate_plan_apply(
workspace_dir.as_str(),
false,
),
)?;
}
DeploymentTarget::SelfHosted(kubernetes, environment) => {
@@ -301,21 +363,29 @@ impl Create for MongoDB {
.downcast_ref::<AWS>()
.unwrap();
let kubernetes_config_file_path = common::kubernetes_config_path(
workspace_dir.as_str(),
environment.organization_id.as_str(),
kubernetes.id(),
aws.access_key_id.as_str(),
aws.secret_access_key.as_str(),
kubernetes.region(),
let kubernetes_config_file_path = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
common::kubernetes_config_path(
workspace_dir.as_str(),
environment.organization_id.as_str(),
kubernetes.id(),
aws.access_key_id.as_str(),
aws.secret_access_key.as_str(),
kubernetes.region(),
),
)?;
let from_dir = format!("{}/common/services/mongodb", self.context.lib_root_dir());
let _ = crate::template::generate_and_copy_all_files_into_dir(
from_dir.as_str(),
workspace_dir.as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
from_dir.as_str(),
workspace_dir.as_str(),
&context,
),
)?;
// render templates
@@ -326,19 +396,26 @@ impl Create for MongoDB {
];
// do exec helm upgrade and return the last deployment status
let helm_history_row = crate::cmd::helm::helm_exec_with_upgrade_history(
kubernetes_config_file_path.as_str(),
environment.namespace(),
helm_release_name.as_str(),
workspace_dir.as_str(),
aws_credentials_envs.clone(),
let helm_history_row = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::cmd::helm::helm_exec_with_upgrade_history(
kubernetes_config_file_path.as_str(),
environment.namespace(),
helm_release_name.as_str(),
workspace_dir.as_str(),
aws_credentials_envs.clone(),
),
)?;
// check deployment status
if helm_history_row.is_none()
|| !helm_history_row.unwrap().is_successfully_deployed()
{
return Err(ServiceError::OnCreateFailed);
return Err(self.engine_error(
EngineErrorCause::Internal,
"MongoDB database fails to be deployed (before start)".into(),
));
}
// check app status
@@ -351,7 +428,16 @@ impl Create for MongoDB {
aws_credentials_envs,
) {
Ok(Some(true)) => {}
_ => return Err(ServiceError::OnCreateFailed),
_ => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"MongoDB database {} with id {} failed to start after several retries",
self.name(),
self.id()
),
));
}
}
}
}
@@ -359,11 +445,11 @@ impl Create for MongoDB {
Ok(())
}
fn on_create_check(&self) -> Result<(), ServiceError> {
fn on_create_check(&self) -> Result<(), EngineError> {
Ok(())
}
fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
warn!("AWS.MongoDB.on_create_error() called for {}", self.name());
self.delete(target, true)
@@ -371,7 +457,7 @@ impl Create for MongoDB {
}
impl Pause for MongoDB {
fn on_pause(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_pause(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
info!("AWS.MongoDB.on_pause() called for {}", self.name());
// TODO how to pause production? - the goal is to reduce cost, but it is possible to pause a production env?
@@ -380,11 +466,11 @@ impl Pause for MongoDB {
Ok(())
}
fn on_pause_check(&self) -> Result<(), ServiceError> {
fn on_pause_check(&self) -> Result<(), EngineError> {
Ok(())
}
fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
warn!("AWS.MongoDB.on_pause_error() called for {}", self.name());
// TODO what to do if there is a pause error?
@@ -394,85 +480,85 @@ impl Pause for MongoDB {
}
impl Delete for MongoDB {
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
info!("AWS.MongoDB.on_delete() called for {}", self.name());
self.delete(target, false)
}
fn on_delete_check(&self) -> Result<(), ServiceError> {
fn on_delete_check(&self) -> Result<(), EngineError> {
Ok(())
}
fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
warn!("AWS.MongoDB.on_create_error() called for {}", self.name());
self.delete(target, true)
}
}
impl crate::cloud_provider::service::Clone for MongoDB {
fn on_clone(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_clone(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
fn on_clone_check(&self) -> Result<(), ServiceError> {
fn on_clone_check(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_clone_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_clone_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
}
impl Upgrade for MongoDB {
fn on_upgrade(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_upgrade(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
fn on_upgrade_check(&self) -> Result<(), ServiceError> {
fn on_upgrade_check(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_upgrade_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_upgrade_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
}
impl Downgrade for MongoDB {
fn on_downgrade(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_downgrade(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
fn on_downgrade_check(&self) -> Result<(), ServiceError> {
fn on_downgrade_check(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_downgrade_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_downgrade_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
}
impl Backup for MongoDB {
fn on_backup(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_backup(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
fn on_backup_check(&self) -> Result<(), ServiceError> {
fn on_backup_check(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_backup_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_backup_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
fn on_restore(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_restore(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
fn on_restore_check(&self) -> Result<(), ServiceError> {
fn on_restore_check(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_restore_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_restore_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
}

View File

@@ -6,14 +6,17 @@ use crate::cloud_provider::aws::{common, AWS};
use crate::cloud_provider::environment::Environment;
use crate::cloud_provider::kubernetes::Kubernetes;
use crate::cloud_provider::service::{
Action, Backup, Create, DatabaseOptions, DatabaseType, Delete, Downgrade, Pause, Service,
ServiceError, ServiceType, StatefulService, Upgrade,
Action, Backup, Create, Database, DatabaseOptions, DatabaseType, Delete, Downgrade, Pause,
Service, ServiceType, StatefulService, Upgrade,
};
use crate::cloud_provider::DeploymentTarget;
use crate::cmd::kubectl::{
kubectl_exec_create_namespace, kubectl_exec_delete_namespace, kubectl_exec_delete_secret,
};
use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY};
use crate::error::{
from_simple_error_to_engine_error, EngineError, EngineErrorCause, EngineErrorScope,
};
use crate::models::Context;
pub struct MySQL {
@@ -58,9 +61,11 @@ impl MySQL {
options,
}
}
fn helm_release_name(&self) -> String {
crate::string::cut(format!("mysql-{}", self.id()), 50)
}
fn workspace_directory(&self) -> String {
crate::fs::workspace_directory(
self.context.workspace_root_dir(),
@@ -68,20 +73,24 @@ impl MySQL {
format!("databases/{}", self.name()),
)
}
fn tera_context(&self, kubernetes: &dyn Kubernetes, environment: &Environment) -> TeraContext {
let mut context = self.default_tera_context(kubernetes, environment);
// FIXME: is there an other way than downcast a pointer?
let cp = kubernetes
.cloud_provider()
.as_any()
.downcast_ref::<AWS>()
.expect("Could not downcast kubernetes.cloud_provider() to AWS");
// we need the kubernetes config file to store tfstates file in kube secrets
let kubernetes_config_file_path = utilities::get_kubernetes_config_path(
self.workspace_directory().as_str(),
kubernetes,
environment,
);
match kubernetes_config_file_path {
Ok(kube_config) => {
context.insert("kubeconfig_path", &kube_config.as_str());
@@ -93,8 +102,12 @@ impl MySQL {
utilities::create_namespace(&environment.namespace(), kube_config.as_str(), aws);
}
Err(e) => error!("Failed to generate the kubernetes config file path: {}", e),
Err(e) => error!(
"Failed to generate the kubernetes config file path: {:?}",
e
),
}
context.insert("namespace", environment.namespace());
context.insert("aws_access_key", &cp.access_key_id);
@@ -119,7 +132,7 @@ impl MySQL {
context
}
fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), ServiceError> {
fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), EngineError> {
let workspace_dir = self.workspace_directory();
match target {
@@ -131,39 +144,58 @@ impl MySQL {
let context = self.tera_context(*kubernetes, *environment);
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(),
&workspace_dir,
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(),
&workspace_dir,
&context,
),
)?;
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/mysql", self.context.lib_root_dir()).as_str(),
workspace_dir.as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/mysql", self.context.lib_root_dir()).as_str(),
workspace_dir.as_str(),
&context,
),
)?;
crate::template::generate_and_copy_all_files_into_dir(
format!(
"{}/aws/charts/external-name-svc",
self.context.lib_root_dir()
)
.as_str(),
format!("{}/{}", workspace_dir, "external-name-svc").as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!(
"{}/aws/charts/external-name-svc",
self.context.lib_root_dir()
)
.as_str(),
format!("{}/{}", workspace_dir, "external-name-svc").as_str(),
&context,
),
)?;
crate::template::generate_and_copy_all_files_into_dir(
format!(
"{}/aws/charts/external-name-svc",
self.context.lib_root_dir()
)
.as_str(),
workspace_dir.as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!(
"{}/aws/charts/external-name-svc",
self.context.lib_root_dir()
)
.as_str(),
workspace_dir.as_str(),
&context,
),
)?;
match crate::cmd::terraform::terraform_exec_with_init_validate_destroy(
workspace_dir.as_str(),
) {
Ok(o) => {
Ok(_) => {
info!("Deleting secrets containing tfstates");
utilities::delete_terraform_tfstate_secret(
*kubernetes,
@@ -171,8 +203,16 @@ impl MySQL {
self.workspace_directory().as_str(),
);
}
//TODO: find a way to raise the error
Err(e) => error!("Error while destroying infrastructure {}", e),
Err(e) => {
let message = format!(
"Error while destroying infrastructure {}",
e.message.unwrap_or("".into())
);
error!("{}", message);
return Err(self.engine_error(EngineErrorCause::Internal, message));
}
}
}
DeploymentTarget::SelfHosted(kubernetes, environment) => {
@@ -180,20 +220,28 @@ impl MySQL {
let selector = format!("app={}", self.name());
if is_error {
let _ = common::get_stateless_resource_information(
*kubernetes,
*environment,
workspace_dir.as_str(),
selector.as_str(),
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
common::get_stateless_resource_information(
*kubernetes,
*environment,
workspace_dir.as_str(),
selector.as_str(),
),
)?;
}
// clean the resource
let _ = common::do_stateless_service_cleanup(
*kubernetes,
*environment,
workspace_dir.as_str(),
helm_release_name.as_str(),
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
common::do_stateless_service_cleanup(
*kubernetes,
*environment,
workspace_dir.as_str(),
helm_release_name.as_str(),
),
)?;
}
}
@@ -246,8 +294,10 @@ impl Service for MySQL {
}
}
impl Database for MySQL {}
impl Create for MySQL {
fn on_create(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
match target {
DeploymentTarget::ManagedServices(kubernetes, environment) => {
// use terraform
@@ -256,29 +306,47 @@ impl Create for MySQL {
let workspace_dir = self.workspace_directory();
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(),
&workspace_dir,
&context,
)?;
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/mysql", self.context.lib_root_dir()).as_str(),
workspace_dir.as_str(),
&context,
)?;
crate::template::generate_and_copy_all_files_into_dir(
format!(
"{}/aws/charts/external-name-svc",
self.context.lib_root_dir()
)
.as_str(),
format!("{}/{}", workspace_dir, "external-name-svc").as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(),
&workspace_dir,
&context,
),
)?;
crate::cmd::terraform::terraform_exec_with_init_validate_plan_apply(
workspace_dir.as_str(),
false,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/mysql", self.context.lib_root_dir()).as_str(),
workspace_dir.as_str(),
&context,
),
)?;
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!(
"{}/aws/charts/external-name-svc",
self.context.lib_root_dir()
)
.as_str(),
format!("{}/{}", workspace_dir, "external-name-svc").as_str(),
&context,
),
)?;
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::cmd::terraform::terraform_exec_with_init_validate_plan_apply(
workspace_dir.as_str(),
false,
),
)?;
}
DeploymentTarget::SelfHosted(kubernetes, environment) => {
@@ -294,21 +362,29 @@ impl Create for MySQL {
.downcast_ref::<AWS>()
.unwrap();
let kubernetes_config_file_path = common::kubernetes_config_path(
workspace_dir.as_str(),
environment.organization_id.as_str(),
kubernetes.id(),
aws.access_key_id.as_str(),
aws.secret_access_key.as_str(),
kubernetes.region(),
let kubernetes_config_file_path = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
common::kubernetes_config_path(
workspace_dir.as_str(),
environment.organization_id.as_str(),
kubernetes.id(),
aws.access_key_id.as_str(),
aws.secret_access_key.as_str(),
kubernetes.region(),
),
)?;
let from_dir = format!("{}/common/services/mysql", self.context.lib_root_dir());
let _ = crate::template::generate_and_copy_all_files_into_dir(
from_dir.as_str(),
workspace_dir.as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
from_dir.as_str(),
workspace_dir.as_str(),
&context,
),
)?;
// render templates
@@ -319,19 +395,26 @@ impl Create for MySQL {
];
// do exec helm upgrade and return the last deployment status
let helm_history_row = crate::cmd::helm::helm_exec_with_upgrade_history(
kubernetes_config_file_path.as_str(),
environment.namespace(),
helm_release_name.as_str(),
workspace_dir.as_str(),
aws_credentials_envs.clone(),
let helm_history_row = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::cmd::helm::helm_exec_with_upgrade_history(
kubernetes_config_file_path.as_str(),
environment.namespace(),
helm_release_name.as_str(),
workspace_dir.as_str(),
aws_credentials_envs.clone(),
),
)?;
// check deployment status
if helm_history_row.is_none()
|| !helm_history_row.unwrap().is_successfully_deployed()
{
return Err(ServiceError::OnCreateFailed);
return Err(self.engine_error(
EngineErrorCause::Internal,
"MySQL database fails to be deployed (before start)".into(),
));
}
// check app status
@@ -344,7 +427,16 @@ impl Create for MySQL {
aws_credentials_envs,
) {
Ok(Some(true)) => {}
_ => return Err(ServiceError::OnCreateFailed),
_ => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"MySQL database {} with id {} failed to start after several retries",
self.name(),
self.id()
),
));
}
}
}
}
@@ -352,12 +444,12 @@ impl Create for MySQL {
Ok(())
}
fn on_create_check(&self) -> Result<(), ServiceError> {
fn on_create_check(&self) -> Result<(), EngineError> {
//FIXME : perform an actual check
Ok(())
}
fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
warn!("AWS.MySQL.on_create_error() called for {}", self.name());
self.delete(target, true)
@@ -365,7 +457,7 @@ impl Create for MySQL {
}
impl Pause for MySQL {
fn on_pause(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_pause(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
info!("AWS.MySQL.on_pause() called for {}", self.name());
// TODO how to pause production? - the goal is to reduce cost, but it is possible to pause a production env?
@@ -374,11 +466,11 @@ impl Pause for MySQL {
Ok(())
}
fn on_pause_check(&self) -> Result<(), ServiceError> {
fn on_pause_check(&self) -> Result<(), EngineError> {
Ok(())
}
fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
warn!("AWS.MySQL.on_pause_error() called for {}", self.name());
// TODO what to do if there is a pause error?
@@ -388,85 +480,85 @@ impl Pause for MySQL {
}
impl Delete for MySQL {
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
info!("AWS.MySQL.on_delete() called for {}", self.name());
self.delete(target, false)
}
fn on_delete_check(&self) -> Result<(), ServiceError> {
fn on_delete_check(&self) -> Result<(), EngineError> {
Ok(())
}
fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
warn!("AWS.MySQL.on_create_error() called for {}", self.name());
self.delete(target, true)
}
}
impl crate::cloud_provider::service::Clone for MySQL {
fn on_clone(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_clone(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
fn on_clone_check(&self) -> Result<(), ServiceError> {
fn on_clone_check(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_clone_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_clone_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
}
impl Upgrade for MySQL {
fn on_upgrade(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_upgrade(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
fn on_upgrade_check(&self) -> Result<(), ServiceError> {
fn on_upgrade_check(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_upgrade_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_upgrade_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
}
impl Downgrade for MySQL {
fn on_downgrade(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_downgrade(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
fn on_downgrade_check(&self) -> Result<(), ServiceError> {
fn on_downgrade_check(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_downgrade_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_downgrade_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
}
impl Backup for MySQL {
fn on_backup(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_backup(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
fn on_backup_check(&self) -> Result<(), ServiceError> {
fn on_backup_check(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_backup_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_backup_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
fn on_restore(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_restore(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
fn on_restore_check(&self) -> Result<(), ServiceError> {
fn on_restore_check(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_restore_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_restore_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
}

View File

@@ -6,10 +6,13 @@ use crate::cloud_provider::environment::Environment;
use crate::cloud_provider::kubernetes::Kubernetes;
use crate::cloud_provider::service::{
Action, Backup, Create, Database, DatabaseOptions, DatabaseType, Delete, Downgrade, Pause,
Service, ServiceError, ServiceType, StatefulService, Upgrade,
Service, ServiceType, StatefulService, Upgrade,
};
use crate::cloud_provider::DeploymentTarget;
use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY};
use crate::error::{
from_simple_error_to_engine_error, EngineError, EngineErrorCause, EngineErrorScope,
};
use crate::models::Context;
pub struct PostgreSQL {
@@ -69,18 +72,21 @@ impl PostgreSQL {
fn tera_context(&self, kubernetes: &dyn Kubernetes, environment: &Environment) -> TeraContext {
let mut context = self.default_tera_context(kubernetes, environment);
// FIXME: is there an other way than downcast a pointer?
let cp = kubernetes
.cloud_provider()
.as_any()
.downcast_ref::<AWS>()
.expect("Could not downcast kubernetes.cloud_provider() to AWS");
// we need the kubernetes config file to store tfstates file in kube secrets
let kubernetes_config_file_path = utilities::get_kubernetes_config_path(
self.workspace_directory().as_str(),
kubernetes,
environment,
);
match kubernetes_config_file_path {
Ok(kube_config) => {
context.insert("kubeconfig_path", &kube_config.as_str());
@@ -92,8 +98,12 @@ impl PostgreSQL {
utilities::create_namespace(&environment.namespace(), kube_config.as_str(), aws);
}
Err(e) => error!("Failed to generate the kubernetes config file path: {}", e),
Err(e) => error!(
"Failed to generate the kubernetes config file path: {:?}",
e
),
}
context.insert("namespace", environment.namespace());
context.insert("aws_access_key", &cp.access_key_id);
@@ -118,7 +128,7 @@ impl PostgreSQL {
context
}
fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), ServiceError> {
fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), EngineError> {
let workspace_dir = self.workspace_directory();
match target {
@@ -130,39 +140,58 @@ impl PostgreSQL {
let context = self.tera_context(*kubernetes, *environment);
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(),
&workspace_dir,
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(),
&workspace_dir,
&context,
),
)?;
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/postgresql", self.context.lib_root_dir()).as_str(),
workspace_dir.as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/postgresql", self.context.lib_root_dir()).as_str(),
workspace_dir.as_str(),
&context,
),
)?;
crate::template::generate_and_copy_all_files_into_dir(
format!(
"{}/aws/charts/external-name-svc",
self.context.lib_root_dir()
)
.as_str(),
format!("{}/{}", workspace_dir, "external-name-svc").as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!(
"{}/aws/charts/external-name-svc",
self.context.lib_root_dir()
)
.as_str(),
format!("{}/{}", workspace_dir, "external-name-svc").as_str(),
&context,
),
)?;
crate::template::generate_and_copy_all_files_into_dir(
format!(
"{}/aws/charts/external-name-svc",
self.context.lib_root_dir()
)
.as_str(),
workspace_dir.as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!(
"{}/aws/charts/external-name-svc",
self.context.lib_root_dir()
)
.as_str(),
workspace_dir.as_str(),
&context,
),
)?;
match crate::cmd::terraform::terraform_exec_with_init_validate_destroy(
workspace_dir.as_str(),
) {
Ok(o) => {
Ok(_) => {
info!("Deleting secrets containing tfstates");
utilities::delete_terraform_tfstate_secret(
*kubernetes,
@@ -170,8 +199,16 @@ impl PostgreSQL {
self.workspace_directory().as_str(),
);
}
//TODO: find a way to raise the error
Err(e) => error!("Error while destroying infrastructure {}", e),
Err(e) => {
let message = format!(
"Error while destroying infrastructure {}",
e.message.unwrap_or("".into())
);
error!("{}", message);
return Err(self.engine_error(EngineErrorCause::Internal, message));
}
}
}
DeploymentTarget::SelfHosted(kubernetes, environment) => {
@@ -179,20 +216,28 @@ impl PostgreSQL {
let selector = format!("app={}", self.name());
if is_error {
let _ = common::get_stateless_resource_information(
*kubernetes,
*environment,
workspace_dir.as_str(),
selector.as_str(),
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
common::get_stateless_resource_information(
*kubernetes,
*environment,
workspace_dir.as_str(),
selector.as_str(),
),
)?;
}
// clean the resource
let _ = common::do_stateless_service_cleanup(
*kubernetes,
*environment,
workspace_dir.as_str(),
helm_release_name.as_str(),
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
common::do_stateless_service_cleanup(
*kubernetes,
*environment,
workspace_dir.as_str(),
helm_release_name.as_str(),
),
)?;
}
}
@@ -248,7 +293,7 @@ impl Service for PostgreSQL {
impl Database for PostgreSQL {}
impl Create for PostgreSQL {
fn on_create(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
info!("AWS.PostgreSQL.on_create() called for {}", self.name());
let workspace_dir = self.workspace_directory();
@@ -261,29 +306,47 @@ impl Create for PostgreSQL {
let workspace_dir = self.workspace_directory();
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(),
&workspace_dir,
&context,
)?;
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/postgresql", self.context.lib_root_dir()).as_str(),
workspace_dir.as_str(),
&context,
)?;
crate::template::generate_and_copy_all_files_into_dir(
format!(
"{}/aws/charts/external-name-svc",
self.context.lib_root_dir()
)
.as_str(),
format!("{}/{}", workspace_dir, "external-name-svc").as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/common", self.context.lib_root_dir()).as_str(),
&workspace_dir,
&context,
),
)?;
crate::cmd::terraform::terraform_exec_with_init_validate_plan_apply(
workspace_dir.as_str(),
false,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!("{}/aws/services/postgresql", self.context.lib_root_dir()).as_str(),
workspace_dir.as_str(),
&context,
),
)?;
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
format!(
"{}/aws/charts/external-name-svc",
self.context.lib_root_dir()
)
.as_str(),
format!("{}/{}", workspace_dir, "external-name-svc").as_str(),
&context,
),
)?;
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::cmd::terraform::terraform_exec_with_init_validate_plan_apply(
workspace_dir.as_str(),
false,
),
)?;
}
DeploymentTarget::SelfHosted(kubernetes, environment) => {
@@ -298,22 +361,30 @@ impl Create for PostgreSQL {
.downcast_ref::<AWS>()
.unwrap();
let kubernetes_config_file_path = common::kubernetes_config_path(
workspace_dir.as_str(),
environment.organization_id.as_str(),
kubernetes.id(),
aws.access_key_id.as_str(),
aws.secret_access_key.as_str(),
kubernetes.region(),
let kubernetes_config_file_path = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
common::kubernetes_config_path(
workspace_dir.as_str(),
environment.organization_id.as_str(),
kubernetes.id(),
aws.access_key_id.as_str(),
aws.secret_access_key.as_str(),
kubernetes.region(),
),
)?;
let from_dir =
format!("{}/common/services/postgresql", self.context.lib_root_dir());
let _ = crate::template::generate_and_copy_all_files_into_dir(
from_dir.as_str(),
workspace_dir.as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
from_dir.as_str(),
workspace_dir.as_str(),
&context,
),
)?;
// render templates
@@ -324,19 +395,26 @@ impl Create for PostgreSQL {
];
// do exec helm upgrade and return the last deployment status
let helm_history_row = crate::cmd::helm::helm_exec_with_upgrade_history(
kubernetes_config_file_path.as_str(),
environment.namespace(),
helm_release_name.as_str(),
workspace_dir.as_str(),
aws_credentials_envs.clone(),
let helm_history_row = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::cmd::helm::helm_exec_with_upgrade_history(
kubernetes_config_file_path.as_str(),
environment.namespace(),
helm_release_name.as_str(),
workspace_dir.as_str(),
aws_credentials_envs.clone(),
),
)?;
// check deployment status
if helm_history_row.is_none()
|| !helm_history_row.unwrap().is_successfully_deployed()
{
return Err(ServiceError::OnCreateFailed);
return Err(self.engine_error(
EngineErrorCause::Internal,
"PostgreSQL database fails to be deployed (before start)".into(),
));
}
// check app status
@@ -349,7 +427,16 @@ impl Create for PostgreSQL {
aws_credentials_envs,
) {
Ok(Some(true)) => {}
_ => return Err(ServiceError::OnCreateFailed),
_ => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"PostgreSQL database {} with id {} failed to start after several retries",
self.name(),
self.id()
),
));
}
}
}
}
@@ -357,11 +444,11 @@ impl Create for PostgreSQL {
Ok(())
}
fn on_create_check(&self) -> Result<(), ServiceError> {
fn on_create_check(&self) -> Result<(), EngineError> {
Ok(())
}
fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
warn!(
"AWS.PostgreSQL.on_create_error() called for {}",
self.name()
@@ -372,7 +459,7 @@ impl Create for PostgreSQL {
}
impl Pause for PostgreSQL {
fn on_pause(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_pause(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
info!("AWS.PostgreSQL.on_pause() called for {}", self.name());
// TODO how to pause production? - the goal is to reduce cost, but it is possible to pause a production env?
@@ -381,11 +468,11 @@ impl Pause for PostgreSQL {
Ok(())
}
fn on_pause_check(&self) -> Result<(), ServiceError> {
fn on_pause_check(&self) -> Result<(), EngineError> {
Ok(())
}
fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
warn!("AWS.PostgreSQL.on_pause_error() called for {}", self.name());
// TODO what to do if there is a pause error?
@@ -395,16 +482,16 @@ impl Pause for PostgreSQL {
}
impl Delete for PostgreSQL {
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
info!("AWS.PostgreSQL.on_delete() called for {}", self.name());
self.delete(target, false)
}
fn on_delete_check(&self) -> Result<(), ServiceError> {
fn on_delete_check(&self) -> Result<(), EngineError> {
Ok(())
}
fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
warn!(
"AWS.PostgreSQL.on_create_error() called for {}",
self.name()
@@ -414,69 +501,69 @@ impl Delete for PostgreSQL {
}
impl crate::cloud_provider::service::Clone for PostgreSQL {
fn on_clone(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_clone(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
fn on_clone_check(&self) -> Result<(), ServiceError> {
fn on_clone_check(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_clone_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_clone_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
}
impl Upgrade for PostgreSQL {
fn on_upgrade(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_upgrade(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
fn on_upgrade_check(&self) -> Result<(), ServiceError> {
fn on_upgrade_check(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_upgrade_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_upgrade_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
}
impl Downgrade for PostgreSQL {
fn on_downgrade(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_downgrade(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
fn on_downgrade_check(&self) -> Result<(), ServiceError> {
fn on_downgrade_check(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_downgrade_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_downgrade_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
}
impl Backup for PostgreSQL {
fn on_backup(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_backup(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
fn on_backup_check(&self) -> Result<(), ServiceError> {
fn on_backup_check(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_backup_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_backup_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
fn on_restore(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_restore(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
fn on_restore_check(&self) -> Result<(), ServiceError> {
fn on_restore_check(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_restore_error(&self, _target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_restore_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
unimplemented!()
}
}

View File

@@ -5,13 +5,14 @@ use crate::cloud_provider::environment::Environment;
use crate::cloud_provider::kubernetes::Kubernetes;
use crate::cmd::kubectl::{kubectl_exec_create_namespace, kubectl_exec_delete_secret};
use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY};
use crate::error::SimpleError;
// generate the kubernetes config path
pub fn get_kubernetes_config_path(
workspace: &str,
kubernetes: &dyn Kubernetes,
environment: &Environment,
) -> Result<String, Error> {
) -> Result<String, SimpleError> {
let aws = kubernetes
.cloud_provider()
.as_any()
@@ -40,7 +41,7 @@ pub fn delete_terraform_tfstate_secret(
kubernetes: &dyn Kubernetes,
environment: &Environment,
workspace_dir: &str,
) -> Result<(), Error> {
) -> Result<(), SimpleError> {
let aws = kubernetes
.cloud_provider()
.as_any()
@@ -67,7 +68,10 @@ pub fn delete_terraform_tfstate_secret(
Ok(())
}
Err(e) => {
error!("Failed to generate the kubernetes config file path: {}", e);
error!(
"Failed to generate the kubernetes config file path: {:?}",
e
);
Err(e)
}
}

View File

@@ -6,11 +6,12 @@ use crate::cloud_provider::aws::{common, AWS};
use crate::cloud_provider::environment::Environment;
use crate::cloud_provider::kubernetes::Kubernetes;
use crate::cloud_provider::service::{
Action, Application, Create, Delete, Pause, Service, ServiceError, ServiceType,
StatelessService,
Action, Application as AApplication, Create, Delete, ExternalService as EExternalService,
Pause, Service, ServiceType, StatelessService,
};
use crate::cloud_provider::DeploymentTarget;
use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY};
use crate::error::{from_simple_error_to_engine_error, EngineError, EngineErrorCause};
use crate::models::Context;
#[derive(Clone, Eq, PartialEq, Hash)]
@@ -92,7 +93,7 @@ impl ExternalService {
context
}
fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), ServiceError> {
fn delete(&self, target: &DeploymentTarget, is_error: bool) -> Result<(), EngineError> {
let (kubernetes, environment) = match target {
DeploymentTarget::ManagedServices(k, env) => (*k, *env),
DeploymentTarget::SelfHosted(k, env) => (*k, *env),
@@ -103,20 +104,28 @@ impl ExternalService {
let selector = format!("app={}", self.name());
if is_error {
let _ = common::get_stateless_resource_information(
kubernetes,
environment,
workspace_dir.as_str(),
selector.as_str(),
let _ = from_simple_error_to_engine_error(
crate::cloud_provider::service::ExternalService::engine_error_scope(self),
self.context.execution_id(),
common::get_stateless_resource_information(
kubernetes,
environment,
workspace_dir.as_str(),
selector.as_str(),
),
)?;
}
// clean the resource
let _ = common::do_stateless_service_cleanup(
kubernetes,
environment,
workspace_dir.as_str(),
helm_release_name.as_str(),
let _ = from_simple_error_to_engine_error(
crate::cloud_provider::service::ExternalService::engine_error_scope(self),
self.context.execution_id(),
common::do_stateless_service_cleanup(
kubernetes,
environment,
workspace_dir.as_str(),
helm_release_name.as_str(),
),
)?;
Ok(())
@@ -180,7 +189,7 @@ impl Service for ExternalService {
}
impl Create for ExternalService {
fn on_create(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
info!(
"AWS.external_service.on_create() called for {}",
self.name()
@@ -200,10 +209,14 @@ impl Create for ExternalService {
let workspace_dir = self.workspace_directory();
let from_dir = format!("{}/common/services/q-job", self.context.lib_root_dir());
let _ = crate::template::generate_and_copy_all_files_into_dir(
from_dir.as_str(),
workspace_dir.as_str(),
&context,
let _ = from_simple_error_to_engine_error(
crate::cloud_provider::service::ExternalService::engine_error_scope(self),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
from_dir.as_str(),
workspace_dir.as_str(),
&context,
),
)?;
// render
@@ -214,28 +227,41 @@ impl Create for ExternalService {
(AWS_SECRET_ACCESS_KEY, aws.secret_access_key.as_str()),
];
let kubernetes_config_file_path = common::kubernetes_config_path(
workspace_dir.as_str(),
environment.organization_id.as_str(),
kubernetes.id(),
aws.access_key_id.as_str(),
aws.secret_access_key.as_str(),
kubernetes.region(),
let kubernetes_config_file_path = from_simple_error_to_engine_error(
crate::cloud_provider::service::ExternalService::engine_error_scope(self),
self.context.execution_id(),
common::kubernetes_config_path(
workspace_dir.as_str(),
environment.organization_id.as_str(),
kubernetes.id(),
aws.access_key_id.as_str(),
aws.secret_access_key.as_str(),
kubernetes.region(),
),
)?;
// do exec helm upgrade and return the last deployment status
let helm_history_row = crate::cmd::helm::helm_exec_with_upgrade_history(
kubernetes_config_file_path.as_str(),
environment.namespace(),
helm_release_name.as_str(),
workspace_dir.as_str(),
aws_credentials_envs.clone(),
let helm_history_row = from_simple_error_to_engine_error(
crate::cloud_provider::service::ExternalService::engine_error_scope(self),
self.context.execution_id(),
crate::cmd::helm::helm_exec_with_upgrade_history(
kubernetes_config_file_path.as_str(),
environment.namespace(),
helm_release_name.as_str(),
workspace_dir.as_str(),
aws_credentials_envs.clone(),
),
)?;
// check deployment status
if helm_history_row.is_none() || !helm_history_row.unwrap().is_successfully_deployed() {
// TODO get pod output by using kubectl and return it into the OnCreateFailed
return Err(ServiceError::OnCreateFailed);
return Err(crate::cloud_provider::service::ExternalService::engine_error(self, EngineErrorCause::User(
"Your External Service didn't start for some reason. \
Are you sure your External Service is correctly running? You can give a try by running \
locally `docker run`. You can also check the External Service log from the web \
interface or the CLI with `qovery log`",
), format!("External Service {} has failed to start ", self.name_with_id()),
));
}
// check job status
@@ -246,17 +272,29 @@ impl Create for ExternalService {
aws_credentials_envs,
) {
Ok(Some(true)) => {}
_ => return Err(ServiceError::OnCreateFailed),
_ => {
return Err(
crate::cloud_provider::service::ExternalService::engine_error(
self,
EngineErrorCause::Internal,
format!(
"External Service {} with id {} failed to start after several retries",
self.name(),
self.id()
),
),
);
}
}
Ok(())
}
fn on_create_check(&self) -> Result<(), ServiceError> {
fn on_create_check(&self) -> Result<(), EngineError> {
Ok(())
}
fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
warn!(
"AWS.external_service.on_create_error() called for {}",
self.name()
@@ -279,30 +317,42 @@ impl Create for ExternalService {
(AWS_SECRET_ACCESS_KEY, aws.secret_access_key.as_str()),
];
let kubernetes_config_file_path = common::kubernetes_config_path(
workspace_dir.as_str(),
environment.organization_id.as_str(),
kubernetes.id(),
aws.access_key_id.as_str(),
aws.secret_access_key.as_str(),
kubernetes.region(),
let kubernetes_config_file_path = from_simple_error_to_engine_error(
crate::cloud_provider::service::ExternalService::engine_error_scope(self),
self.context.execution_id(),
common::kubernetes_config_path(
workspace_dir.as_str(),
environment.organization_id.as_str(),
kubernetes.id(),
aws.access_key_id.as_str(),
aws.secret_access_key.as_str(),
kubernetes.region(),
),
)?;
let helm_release_name = self.helm_release_name();
let history_rows = crate::cmd::helm::helm_exec_history(
kubernetes_config_file_path.as_str(),
environment.namespace(),
helm_release_name.as_str(),
aws_credentials_envs.clone(),
)?;
if history_rows.len() == 1 {
crate::cmd::helm::helm_exec_uninstall(
let history_rows = from_simple_error_to_engine_error(
crate::cloud_provider::service::ExternalService::engine_error_scope(self),
self.context.execution_id(),
crate::cmd::helm::helm_exec_history(
kubernetes_config_file_path.as_str(),
environment.namespace(),
helm_release_name.as_str(),
aws_credentials_envs.clone(),
),
)?;
if history_rows.len() == 1 {
from_simple_error_to_engine_error(
crate::cloud_provider::service::ExternalService::engine_error_scope(self),
self.context.execution_id(),
crate::cmd::helm::helm_exec_uninstall(
kubernetes_config_file_path.as_str(),
environment.namespace(),
helm_release_name.as_str(),
aws_credentials_envs.clone(),
),
)?;
}
@@ -311,16 +361,16 @@ impl Create for ExternalService {
}
impl Pause for ExternalService {
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
info!("AWS.external_service.on_pause() called for {}", self.name());
self.delete(target, false)
}
fn on_pause_check(&self) -> Result<(), ServiceError> {
fn on_pause_check(&self) -> Result<(), EngineError> {
Ok(())
}
fn on_pause_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_pause_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
warn!(
"AWS.external_service.on_pause_error() called for {}",
self.name()
@@ -330,7 +380,7 @@ impl Pause for ExternalService {
}
impl Delete for ExternalService {
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
info!(
"AWS.external_service.on_delete() called for {}",
self.name()
@@ -338,11 +388,11 @@ impl Delete for ExternalService {
self.delete(target, false)
}
fn on_delete_check(&self) -> Result<(), ServiceError> {
fn on_delete_check(&self) -> Result<(), EngineError> {
Ok(())
}
fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
warn!(
"AWS.external_service.on_delete_error() called for {}",
self.name()

View File

@@ -16,20 +16,20 @@ use crate::cloud_provider::aws::kubernetes::node::Node;
use crate::cloud_provider::aws::{common, AWS};
use crate::cloud_provider::environment::Environment;
use crate::cloud_provider::kubernetes::{
check_kubernetes_has_enough_resources_to_deploy_environment, Kind, Kubernetes, KubernetesError,
KubernetesNode, Resources,
check_kubernetes_has_enough_resources_to_deploy_environment, Kind, Kubernetes, KubernetesNode,
Resources,
};
use crate::cloud_provider::service::{Service, ServiceType};
use crate::cloud_provider::{CloudProvider, DeploymentTarget};
use crate::cmd;
use crate::cmd::helm::helm_uninstall_list;
use crate::cmd::kubectl::{kubectl_exec_delete_namespace, kubectl_exec_get_all_namespaces};
use crate::cmd::utilities::CmdError;
use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY};
use crate::deletion_utilities::{get_firsts_namespaces_to_delete, get_qovery_managed_namespaces};
use crate::dns_provider::cloudflare::Cloudflare;
use crate::dns_provider::DnsProvider;
use crate::dns_provider::Kind::CLOUDFLARE;
use crate::error::{from_simple_error_to_engine_error, EngineError, EngineErrorCause};
use crate::fs::workspace_directory;
use crate::models::{
Context, Listener, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressListener,
@@ -348,7 +348,7 @@ impl<'a> Kubernetes for EKS<'a> {
self.dns_provider
}
fn is_valid(&self) -> Result<(), KubernetesError> {
fn is_valid(&self) -> Result<(), EngineError> {
Ok(())
}
@@ -360,20 +360,24 @@ impl<'a> Kubernetes for EKS<'a> {
&self.listeners
}
fn resources(&self, environment: &Environment) -> Result<Resources, KubernetesError> {
fn resources(&self, environment: &Environment) -> Result<Resources, EngineError> {
let aws = self
.cloud_provider()
.as_any()
.downcast_ref::<AWS>()
.unwrap();
let kubernetes_config_file_path = common::kubernetes_config_path(
self.context.workspace_root_dir(),
environment.organization_id.as_str(),
&self.id,
aws.access_key_id.as_str(),
aws.secret_access_key.as_str(),
self.region(),
let kubernetes_config_file_path = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
common::kubernetes_config_path(
self.context.workspace_root_dir(),
environment.organization_id.as_str(),
&self.id,
aws.access_key_id.as_str(),
aws.secret_access_key.as_str(),
self.region(),
),
)?;
let aws_credentials_envs = vec![
@@ -381,8 +385,11 @@ impl<'a> Kubernetes for EKS<'a> {
(AWS_SECRET_ACCESS_KEY, aws.secret_access_key.as_str()),
];
let nodes =
cmd::kubectl::kubectl_exec_get_node(kubernetes_config_file_path, aws_credentials_envs)?;
let nodes = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
cmd::kubectl::kubectl_exec_get_node(kubernetes_config_file_path, aws_credentials_envs),
)?;
let mut resources = Resources {
free_cpu: 0.0,
@@ -413,7 +420,7 @@ impl<'a> Kubernetes for EKS<'a> {
Ok(resources)
}
fn on_create(&self) -> Result<(), KubernetesError> {
fn on_create(&self) -> Result<(), EngineError> {
info!("EKS.on_create() called for {}", self.name());
let listeners_helper = ListenersHelper::new(&self.listeners);
@@ -439,57 +446,78 @@ impl<'a> Kubernetes for EKS<'a> {
// generate terraform files and copy them into temp dir
let context = self.tera_context();
let _ = crate::template::generate_and_copy_all_files_into_dir(
self.template_directory.as_str(),
temp_dir.as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
self.template_directory.as_str(),
temp_dir.as_str(),
&context,
),
)?;
// copy lib/common/bootstrap/charts directory (and sub directory) into the lib/aws/bootstrap/common/charts directory.
// this is due to the required dependencies of lib/aws/bootstrap/*.tf files
let common_charts_temp_dir = format!("{}/common/charts", temp_dir.as_str());
let _ = crate::template::copy_non_template_files(
format!("{}/common/bootstrap/charts", self.context.lib_root_dir()),
common_charts_temp_dir.as_str(),
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::copy_non_template_files(
format!("{}/common/bootstrap/charts", self.context.lib_root_dir()),
common_charts_temp_dir.as_str(),
),
)?;
let _ = crate::cmd::terraform::terraform_exec_with_init_validate_plan_apply(
temp_dir.as_str(),
true,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::cmd::terraform::terraform_exec_with_init_validate_plan_apply(
temp_dir.as_str(),
true,
),
)?;
Ok(())
}
fn on_create_error(&self) -> Result<(), KubernetesError> {
fn on_create_error(&self) -> Result<(), EngineError> {
warn!("EKS.on_create_error() called for {}", self.name());
// FIXME
Err(KubernetesError::Error)
Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"{} Kubernetes cluster rollback not implemented",
self.name()
),
))
}
fn on_upgrade(&self) -> Result<(), KubernetesError> {
fn on_upgrade(&self) -> Result<(), EngineError> {
info!("EKS.on_upgrade() called for {}", self.name());
unimplemented!()
}
fn on_upgrade_error(&self) -> Result<(), KubernetesError> {
fn on_upgrade_error(&self) -> Result<(), EngineError> {
warn!("EKS.on_upgrade_error() called for {}", self.name());
unimplemented!()
}
fn on_downgrade(&self) -> Result<(), KubernetesError> {
fn on_downgrade(&self) -> Result<(), EngineError> {
info!("EKS.on_downgrade() called for {}", self.name());
unimplemented!()
}
fn on_downgrade_error(&self) -> Result<(), KubernetesError> {
fn on_downgrade_error(&self) -> Result<(), EngineError> {
warn!("EKS.on_downgrade_error() called for {}", self.name());
unimplemented!()
}
fn on_delete(&self) -> Result<(), KubernetesError> {
fn on_delete(&self) -> Result<(), EngineError> {
info!("EKS.on_delete() called for {}", self.name());
let listeners_helper = ListenersHelper::new(&self.listeners);
listeners_helper.delete_in_progress(ProgressInfo::new(
ProgressScope::Infrastructure {
execution_id: self.context.execution_id().to_string(),
@@ -511,19 +539,30 @@ impl<'a> Kubernetes for EKS<'a> {
// generate terraform files and copy them into temp dir
let context = self.tera_context();
let _ = crate::template::generate_and_copy_all_files_into_dir(
self.template_directory.as_str(),
temp_dir.as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
self.template_directory.as_str(),
temp_dir.as_str(),
&context,
),
)?;
// copy lib/common/bootstrap/charts directory (and sub directory) into the lib/aws/bootstrap/common/charts directory.
// this is due to the required dependencies of lib/aws/bootstrap/*.tf files
let common_charts_temp_dir = format!("{}/common/charts", temp_dir.as_str());
let _ = crate::template::copy_non_template_files(
format!("{}/common/bootstrap/charts", self.context.lib_root_dir()),
common_charts_temp_dir.as_str(),
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::copy_non_template_files(
format!("{}/common/bootstrap/charts", self.context.lib_root_dir()),
common_charts_temp_dir.as_str(),
),
)?;
let aws_credentials_envs = vec![
(
AWS_ACCESS_KEY_ID,
@@ -535,14 +574,19 @@ impl<'a> Kubernetes for EKS<'a> {
),
];
let kubernetes_config_file_path = kubernetes_config_path(
self.context.workspace_root_dir(),
self.cloud_provider.organization_id.as_str(),
self.id(),
self.cloud_provider.access_key_id.as_str(),
self.cloud_provider.secret_access_key.as_str(),
self.region(),
let kubernetes_config_file_path = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
kubernetes_config_path(
self.context.workspace_root_dir(),
self.cloud_provider.organization_id.as_str(),
self.id(),
self.cloud_provider.access_key_id.as_str(),
self.cloud_provider.secret_access_key.as_str(),
self.region(),
),
)?;
let all_namespaces = kubectl_exec_get_all_namespaces(
kubernetes_config_file_path,
aws_credentials_envs.clone(),
@@ -553,15 +597,20 @@ impl<'a> Kubernetes for EKS<'a> {
Ok(namespace_vec) => {
let namespaces_as_str = namespace_vec.iter().map(std::ops::Deref::deref).collect();
let namespaces_to_delete = get_firsts_namespaces_to_delete(namespaces_as_str);
info!("Deleting non Qovery Namespaces");
for namespace_to_delete in namespaces_to_delete.iter() {
let kubernetes_config_file_path0 = kubernetes_config_path(
self.context.workspace_root_dir(),
self.cloud_provider.organization_id.as_str(),
self.id(),
self.cloud_provider.access_key_id.as_str(),
self.cloud_provider.secret_access_key.as_str(),
self.region(),
let kubernetes_config_file_path0 = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
kubernetes_config_path(
self.context.workspace_root_dir(),
self.cloud_provider.organization_id.as_str(),
self.id(),
self.cloud_provider.access_key_id.as_str(),
self.cloud_provider.secret_access_key.as_str(),
self.region(),
),
)?;
let deletion = cmd::kubectl::kubectl_exec_delete_namespace(
@@ -569,9 +618,10 @@ impl<'a> Kubernetes for EKS<'a> {
namespace_to_delete,
aws_credentials_envs.clone(),
);
match deletion {
Ok(out) => info!("Namespace {} is deleted", namespace_to_delete),
Err(e) => {
Ok(_) => info!("Namespace {} is deleted", namespace_to_delete),
Err(_) => {
error!(
"Can't delete the namespace {}, quiting now",
namespace_to_delete
@@ -580,17 +630,29 @@ impl<'a> Kubernetes for EKS<'a> {
}
}
}
Err(e) => error!("Error while getting all namespaces {}", e),
Err(e) => error!(
"Error while getting all namespaces for Kubernetes cluster {}: error {:?}",
self.name_with_id(),
e.message
),
}
info!("Deleting Qovery managed Namespaces");
let kubernetes_config_file_path2 = kubernetes_config_path(
self.context.workspace_root_dir(),
self.cloud_provider.organization_id.as_str(),
self.id(),
self.cloud_provider.access_key_id.as_str(),
self.cloud_provider.secret_access_key.as_str(),
self.region(),
let kubernetes_config_file_path2 = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
kubernetes_config_path(
self.context.workspace_root_dir(),
self.cloud_provider.organization_id.as_str(),
self.id(),
self.cloud_provider.access_key_id.as_str(),
self.cloud_provider.secret_access_key.as_str(),
self.region(),
),
)?;
// TODO use label instead fixed names
let mut qovery_namespaces = get_qovery_managed_namespaces();
for qovery_namespace in qovery_namespaces.iter() {
@@ -600,8 +662,8 @@ impl<'a> Kubernetes for EKS<'a> {
aws_credentials_envs.clone(),
);
match deletion {
Ok(out) => info!("Namespace {} is fully deleted", qovery_namespace),
Err(e) => {
Ok(_) => info!("Namespace {} is fully deleted", qovery_namespace),
Err(_) => {
error!(
"Can't delete the namespace {}, quiting now",
qovery_namespace
@@ -609,6 +671,7 @@ impl<'a> Kubernetes for EKS<'a> {
}
}
}
info!("Delete all remaining deployed helm applications");
match cmd::helm::helm_list(&kubernetes_config_file_path2, aws_credentials_envs.clone()) {
@@ -619,15 +682,20 @@ impl<'a> Kubernetes for EKS<'a> {
aws_credentials_envs.clone(),
);
}
Err(e) => error!("Unable to get helm list"),
Err(_) => error!("Unable to get helm list"),
}
info!("Running Terraform destroy");
let terraform_result =
cmd::terraform::terraform_exec_with_init_validate_destroy(temp_dir.as_str())?;
let terraform_result = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
cmd::terraform::terraform_exec_with_init_validate_destroy(temp_dir.as_str()),
);
// we should delete the bucket containing the kubeconfig after
// to prevent to loose connection from terraform to kube cluster
match terraform_result {
() => {
Ok(_) => {
info!("Deleting S3 Bucket containing Kubeconfig");
let s3_kubeconfig_bucket = get_s3_kubeconfig_bucket_name(self.id.clone());
let _region = Region::from_str(self.region()).unwrap();
@@ -645,7 +713,7 @@ impl<'a> Kubernetes for EKS<'a> {
Ok(())
}
fn on_delete_error(&self) -> Result<(), KubernetesError> {
fn on_delete_error(&self) -> Result<(), EngineError> {
warn!("EKS.on_delete_error() called for {}", self.name());
// FIXME What should we do if something goes wrong while deleting the cluster?
@@ -653,7 +721,7 @@ impl<'a> Kubernetes for EKS<'a> {
Ok(())
}
fn deploy_environment(&self, environment: &Environment) -> Result<(), KubernetesError> {
fn deploy_environment(&self, environment: &Environment) -> Result<(), EngineError> {
info!("EKS.deploy_environment() called for {}", self.name());
let listeners_helper = ListenersHelper::new(&self.listeners);
@@ -706,7 +774,7 @@ impl<'a> Kubernetes for EKS<'a> {
self.context.execution_id(),
));
return Err(KubernetesError::Deploy(err));
return Err(err);
}
_ => {
listeners_helper.start_in_progress(ProgressInfo::new(
@@ -761,7 +829,7 @@ impl<'a> Kubernetes for EKS<'a> {
self.context.execution_id(),
));
return Err(KubernetesError::Deploy(err));
return Err(err);
}
_ => {
listeners_helper.start_in_progress(ProgressInfo::new(
@@ -814,7 +882,7 @@ impl<'a> Kubernetes for EKS<'a> {
self.context.execution_id(),
));
return Err(KubernetesError::Deploy(err));
return Err(err);
}
_ => {
listeners_helper.started(ProgressInfo::new(
@@ -866,7 +934,7 @@ impl<'a> Kubernetes for EKS<'a> {
self.context.execution_id(),
));
return Err(KubernetesError::Deploy(err));
return Err(err);
}
_ => {
listeners_helper.start_in_progress(ProgressInfo::new(
@@ -886,7 +954,7 @@ impl<'a> Kubernetes for EKS<'a> {
Ok(())
}
fn deploy_environment_error(&self, environment: &Environment) -> Result<(), KubernetesError> {
fn deploy_environment_error(&self, environment: &Environment) -> Result<(), EngineError> {
warn!("EKS.deploy_environment_error() called for {}", self.name());
let listeners_helper = ListenersHelper::new(&self.listeners);
@@ -947,7 +1015,7 @@ impl<'a> Kubernetes for EKS<'a> {
self.context.execution_id(),
));
return Err(KubernetesError::Deploy(err));
return Err(err);
}
_ => {
listeners_helper.start_in_progress(ProgressInfo::new(
@@ -1002,7 +1070,7 @@ impl<'a> Kubernetes for EKS<'a> {
self.context.execution_id(),
));
return Err(KubernetesError::Deploy(err));
return Err(err);
}
_ => {
listeners_helper.start_in_progress(ProgressInfo::new(
@@ -1022,7 +1090,7 @@ impl<'a> Kubernetes for EKS<'a> {
Ok(())
}
fn pause_environment(&self, environment: &Environment) -> Result<(), KubernetesError> {
fn pause_environment(&self, environment: &Environment) -> Result<(), EngineError> {
info!("EKS.pause_environment() called for {}", self.name());
let listeners_helper = ListenersHelper::new(&self.listeners);
@@ -1047,7 +1115,7 @@ impl<'a> Kubernetes for EKS<'a> {
err
);
return Err(KubernetesError::Pause(err));
return Err(err);
}
_ => {}
}
@@ -1066,7 +1134,7 @@ impl<'a> Kubernetes for EKS<'a> {
err
);
return Err(KubernetesError::Pause(err));
return Err(err);
}
_ => {}
}
@@ -1083,7 +1151,7 @@ impl<'a> Kubernetes for EKS<'a> {
err
);
return Err(KubernetesError::Pause(err));
return Err(err);
}
_ => {}
}
@@ -1099,7 +1167,7 @@ impl<'a> Kubernetes for EKS<'a> {
err
);
return Err(KubernetesError::Pause(err));
return Err(err);
}
_ => {}
}
@@ -1108,12 +1176,12 @@ impl<'a> Kubernetes for EKS<'a> {
Ok(())
}
fn pause_environment_error(&self, _environment: &Environment) -> Result<(), KubernetesError> {
fn pause_environment_error(&self, _environment: &Environment) -> Result<(), EngineError> {
warn!("EKS.pause_environment_error() called for {}", self.name());
Ok(())
}
fn delete_environment(&self, environment: &Environment) -> Result<(), KubernetesError> {
fn delete_environment(&self, environment: &Environment) -> Result<(), EngineError> {
info!("EKS.delete_environment() called for {}", self.name());
let listeners_helper = ListenersHelper::new(&self.listeners);
@@ -1139,7 +1207,7 @@ impl<'a> Kubernetes for EKS<'a> {
err
);
return Err(KubernetesError::Delete(err));
return Err(err);
}
_ => {}
}
@@ -1158,7 +1226,7 @@ impl<'a> Kubernetes for EKS<'a> {
err
);
return Err(KubernetesError::Delete(err));
return Err(err);
}
_ => {}
}
@@ -1175,7 +1243,7 @@ impl<'a> Kubernetes for EKS<'a> {
err
);
return Err(KubernetesError::Delete(err));
return Err(err);
}
_ => {}
}
@@ -1191,7 +1259,7 @@ impl<'a> Kubernetes for EKS<'a> {
err
);
return Err(KubernetesError::Delete(err));
return Err(err);
}
_ => {}
}
@@ -1208,13 +1276,17 @@ impl<'a> Kubernetes for EKS<'a> {
),
];
let kubernetes_config_file_path = common::kubernetes_config_path(
&self.context.workspace_root_dir(),
&environment.organization_id.as_str(),
&self.id,
&self.cloud_provider.access_key_id.as_str(),
&self.cloud_provider.secret_access_key.as_str(),
&self.region.name(),
let kubernetes_config_file_path = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
common::kubernetes_config_path(
&self.context.workspace_root_dir(),
&environment.organization_id.as_str(),
&self.id,
&self.cloud_provider.access_key_id.as_str(),
&self.cloud_provider.secret_access_key.as_str(),
&self.region.name(),
),
)?;
kubectl_exec_delete_namespace(
@@ -1226,7 +1298,7 @@ impl<'a> Kubernetes for EKS<'a> {
Ok(())
}
fn delete_environment_error(&self, _environment: &Environment) -> Result<(), KubernetesError> {
fn delete_environment_error(&self, _environment: &Environment) -> Result<(), EngineError> {
warn!("EKS.delete_environment_error() called for {}", self.name());
Ok(())
}

View File

@@ -5,7 +5,8 @@ use rusoto_core::{Client, HttpClient, Region};
use rusoto_credential::StaticProvider;
use rusoto_sts::{GetCallerIdentityRequest, Sts, StsClient};
use crate::cloud_provider::{CloudProvider, CloudProviderError, Kind, TerraformStateCredentials};
use crate::cloud_provider::{CloudProvider, EngineError, Kind, TerraformStateCredentials};
use crate::error::EngineErrorCause;
use crate::models::{Context, Listener, Listeners, ProgressListener};
use crate::runtime::async_run;
@@ -85,13 +86,20 @@ impl CloudProvider for AWS {
self.name.as_str()
}
fn is_valid(&self) -> Result<(), CloudProviderError> {
fn is_valid(&self) -> Result<(), EngineError> {
let client = StsClient::new_with_client(self.client(), Region::default());
let s = async_run(client.get_caller_identity(GetCallerIdentityRequest::default()));
match s {
Ok(_x) => Ok(()),
Err(err) => Err(CloudProviderError::from(err)),
Err(_) => {
return Err(
self.engine_error(
EngineErrorCause::User("Your AWS account seems to be no longer valid (bad Credentials). \
Please contact your Organization administrator to fix or change the Credentials."),
format!("failed to login to AWS {}", self.name_with_id()))
);
}
}
}

View File

@@ -1,21 +1,24 @@
use crate::cloud_provider::aws::{common, AWS};
use crate::cloud_provider::environment::Environment;
use crate::cloud_provider::kubernetes::Kubernetes;
use crate::cloud_provider::service::{
Action, Create, Delete, Pause, Router as RRouter, Service, ServiceError, ServiceType,
StatelessService,
};
use crate::cloud_provider::DeploymentTarget;
use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY};
use crate::models::{
Context, Listeners, ListenersHelper, Metadata, ProgressInfo, ProgressLevel, ProgressScope,
};
use dns_lookup::lookup_host;
use retry::delay::{Fibonacci, Fixed};
use retry::OperationResult;
use serde::{Deserialize, Serialize};
use tera::Context as TeraContext;
use crate::cloud_provider::aws::{common, AWS};
use crate::cloud_provider::environment::Environment;
use crate::cloud_provider::kubernetes::Kubernetes;
use crate::cloud_provider::service::{
Action, Create, Delete, Pause, Router as RRouter, Service, ServiceType, StatelessService,
};
use crate::cloud_provider::DeploymentTarget;
use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY};
use crate::error::{
from_simple_error_to_engine_error, EngineError, EngineErrorCause, SimpleError, SimpleErrorKind,
};
use crate::models::{
Context, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope,
};
pub struct Router {
context: Context,
id: String,
@@ -219,7 +222,7 @@ impl Router {
context
}
fn delete(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let (kubernetes, environment) = match target {
DeploymentTarget::ManagedServices(k, env) => (*k, *env),
DeploymentTarget::SelfHosted(k, env) => (*k, *env),
@@ -228,11 +231,15 @@ impl Router {
let workspace_dir = self.workspace_directory();
let helm_release_name = self.helm_release_name();
let _ = common::do_stateless_service_cleanup(
kubernetes,
environment,
workspace_dir.as_str(),
helm_release_name.as_str(),
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
common::do_stateless_service_cleanup(
kubernetes,
environment,
workspace_dir.as_str(),
helm_release_name.as_str(),
),
)?;
Ok(())
@@ -282,7 +289,7 @@ impl Service for Router {
}
impl crate::cloud_provider::service::Router for Router {
fn check_domains(&self) -> Result<(), ServiceError> {
fn check_domains(&self) -> Result<(), EngineError> {
let check_result = retry::retry(Fibonacci::from_millis(3000).take(10), || {
// TODO send information back to the core
info!("check custom domain {}", self.default_domain.as_str());
@@ -299,7 +306,15 @@ impl crate::cloud_provider::service::Router for Router {
match check_result {
Ok(_) => {}
Err(_) => return Err(ServiceError::CheckFailed),
Err(_) => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"domain {} is still not ready after several retries",
self.default_domain.as_str()
),
))
}
}
Ok(())
@@ -309,7 +324,7 @@ impl crate::cloud_provider::service::Router for Router {
impl StatelessService for Router {}
impl Create for Router {
fn on_create(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
info!("AWS.router.on_create() called for {}", self.name());
let (kubernetes, environment) = match target {
DeploymentTarget::ManagedServices(k, env) => (*k, *env),
@@ -325,13 +340,17 @@ impl Create for Router {
let workspace_dir = self.workspace_directory();
let helm_release_name = self.helm_release_name();
let kubernetes_config_file_path = common::kubernetes_config_path(
workspace_dir.as_str(),
environment.organization_id.as_str(),
kubernetes.id(),
aws.access_key_id.as_str(),
aws.secret_access_key.as_str(),
kubernetes.region(),
let kubernetes_config_file_path = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
common::kubernetes_config_path(
workspace_dir.as_str(),
environment.organization_id.as_str(),
kubernetes.id(),
aws.access_key_id.as_str(),
aws.secret_access_key.as_str(),
kubernetes.region(),
),
)?;
// respect order - getting the context here and not before is mandatory
@@ -349,33 +368,50 @@ impl Create for Router {
);
let from_dir = format!("{}/common/chart_values", self.context.lib_root_dir());
let _ = crate::template::generate_and_copy_all_files_into_dir(
from_dir.as_str(),
into_dir.as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
from_dir.as_str(),
into_dir.as_str(),
&context,
),
)?;
let _ = crate::template::copy_non_template_files(
format!(
"{}/common/charts/nginx-ingress",
self.context().lib_root_dir()
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::copy_non_template_files(
format!(
"{}/common/charts/nginx-ingress",
self.context().lib_root_dir()
),
into_dir.as_str(),
),
into_dir.as_str(),
)?;
// do exec helm upgrade and return the last deployment status
let helm_history_row = crate::cmd::helm::helm_exec_with_upgrade_history_with_override(
kubernetes_config_file_path.as_str(),
environment.namespace(),
format!("custom-{}", helm_release_name).as_str(),
into_dir.as_str(),
format!("{}/nginx-ingress.yaml", into_dir.as_str()).as_str(),
self.aws_credentials_envs(aws).to_vec(),
let helm_history_row = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::cmd::helm::helm_exec_with_upgrade_history_with_override(
kubernetes_config_file_path.as_str(),
environment.namespace(),
format!("custom-{}", helm_release_name).as_str(),
into_dir.as_str(),
format!("{}/nginx-ingress.yaml", into_dir.as_str()).as_str(),
self.aws_credentials_envs(aws).to_vec(),
),
)?;
// check deployment status
if helm_history_row.is_none() || !helm_history_row.unwrap().is_successfully_deployed() {
return Err(ServiceError::OnCreateFailed);
return Err(self.engine_error(
EngineErrorCause::Internal,
"Router has failed to be deployed".into(),
));
}
// waiting for the nlb, it should be deploy to get fqdn
let external_ingress_hostname_custom_result =
retry::retry(Fibonacci::from_millis(3000).take(10), || {
@@ -390,6 +426,7 @@ impl Create for Router {
.as_str(),
self.aws_credentials_envs(aws).to_vec(),
);
match external_ingress_hostname_custom {
Ok(external_ingress_hostname_custom) => {
OperationResult::Ok(external_ingress_hostname_custom)
@@ -402,38 +439,51 @@ impl Create for Router {
}
}
});
match external_ingress_hostname_custom_result {
Ok(elb) => {
//put it in the context
context.insert("nlb_ingress_hostname", &elb);
}
Err(e) => error!("Error getting the NLB endpoint to be able to configure TLS"),
Err(_) => error!("Error getting the NLB endpoint to be able to configure TLS"),
}
}
let from_dir = format!("{}/aws/charts/q-ingress-tls", self.context.lib_root_dir());
let _ = crate::template::generate_and_copy_all_files_into_dir(
from_dir.as_str(),
workspace_dir.as_str(),
&context,
let _ = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::template::generate_and_copy_all_files_into_dir(
from_dir.as_str(),
workspace_dir.as_str(),
&context,
),
)?;
// do exec helm upgrade and return the last deployment status
let helm_history_row = crate::cmd::helm::helm_exec_with_upgrade_history(
kubernetes_config_file_path.as_str(),
environment.namespace(),
helm_release_name.as_str(),
workspace_dir.as_str(),
self.aws_credentials_envs(aws).to_vec(),
let helm_history_row = from_simple_error_to_engine_error(
self.engine_error_scope(),
self.context.execution_id(),
crate::cmd::helm::helm_exec_with_upgrade_history(
kubernetes_config_file_path.as_str(),
environment.namespace(),
helm_release_name.as_str(),
workspace_dir.as_str(),
self.aws_credentials_envs(aws).to_vec(),
),
)?;
if helm_history_row.is_none() || !helm_history_row.unwrap().is_successfully_deployed() {
return Err(ServiceError::OnCreateFailed);
return Err(self.engine_error(
EngineErrorCause::Internal,
"Router has failed to be deployed".into(),
));
}
Ok(())
}
fn on_create_check(&self) -> Result<(), ServiceError> {
fn on_create_check(&self) -> Result<(), EngineError> {
let check_result = retry::retry(Fixed::from_millis(3000).take(60), || {
let rs_ips = lookup_host(self.default_domain.as_str());
match rs_ips {
@@ -447,58 +497,63 @@ impl Create for Router {
}
}
});
match check_result {
Ok(out) => Ok(()),
Err(e) => {
error!("While checking the DNS propagation");
Ok(_) => Ok(()),
Err(_) => {
let message = "While checking the DNS propagation";
error!("{}", message);
let listeners_helper = ListenersHelper::new(&self.listeners);
listeners_helper.error(ProgressInfo::new(
ProgressScope::Router {
id: self.id.to_string(),
id: self.id().into(),
},
ProgressLevel::Error,
Some("DNS propagation goes wrong."),
self.context.execution_id(),
));
Err(ServiceError::CheckFailed)
Err(self.engine_error(EngineErrorCause::Internal, message.into()))
}
}
}
fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
warn!("AWS.router.on_create_error() called for {}", self.name());
self.delete(target)
}
}
impl Pause for Router {
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
info!("AWS.router.on_pause() called for {}", self.name());
self.delete(target)
}
fn on_pause_check(&self) -> Result<(), ServiceError> {
fn on_pause_check(&self) -> Result<(), EngineError> {
warn!("AWS.router.on_pause_error() called for {}", self.name());
// TODO check resource has been cleaned?
Ok(())
}
fn on_pause_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_pause_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
self.delete(target)
}
}
impl Delete for Router {
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
info!("AWS.router.on_delete() called for {}", self.name());
self.delete(target)
}
fn on_delete_check(&self) -> Result<(), ServiceError> {
fn on_delete_check(&self) -> Result<(), EngineError> {
Ok(())
}
fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError> {
fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
warn!("AWS.router.on_delete_error() called for {}", self.name());
self.delete(target)
}

View File

@@ -5,7 +5,8 @@ use std::rc::Rc;
use digitalocean::DigitalOcean;
use crate::cloud_provider::{CloudProvider, CloudProviderError, Kind, TerraformStateCredentials};
use crate::cloud_provider::{CloudProvider, Kind, TerraformStateCredentials};
use crate::error::EngineError;
use crate::models::{Context, Listener, ProgressListener};
pub struct DO {
@@ -49,7 +50,7 @@ impl CloudProvider for DO {
unimplemented!()
}
fn is_valid(&self) -> Result<(), CloudProviderError> {
fn is_valid(&self) -> Result<(), EngineError> {
unimplemented!()
}

View File

@@ -1,4 +1,5 @@
use crate::cloud_provider::service::{ServiceError, StatefulService, StatelessService};
use crate::cloud_provider::service::{StatefulService, StatelessService};
use crate::error::EngineError;
use crate::unit_conversion::cpu_string_to_float;
pub struct Environment {
@@ -38,7 +39,7 @@ impl Environment {
self.namespace.as_str()
}
pub fn is_valid(&self) -> Result<(), ServiceError> {
pub fn is_valid(&self) -> Result<(), EngineError> {
for service in self.stateful_services.iter() {
match service.is_valid() {
Err(err) => return Err(err),

View File

@@ -1,7 +1,7 @@
use std::any::Any;
use std::rc::Rc;
use crate::cloud_provider::{CloudProvider, CloudProviderError, Kind, TerraformStateCredentials};
use crate::cloud_provider::{CloudProvider, Kind, TerraformStateCredentials};
use crate::error::EngineError;
use crate::models::{Context, Listener, ProgressListener};
pub struct GCP {
@@ -43,7 +43,7 @@ impl<'x> CloudProvider for GCP {
self.name.as_str()
}
fn is_valid(&self) -> Result<(), CloudProviderError> {
fn is_valid(&self) -> Result<(), EngineError> {
Ok(())
}

View File

@@ -1,44 +1,54 @@
use std::any::Any;
use std::process::ExitStatus;
use std::rc::Rc;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::cloud_provider::environment::Environment;
use crate::cloud_provider::service::ServiceError;
use crate::cloud_provider::CloudProvider;
use crate::cmd::utilities::CmdError;
use crate::dns_provider::DnsProvider;
use crate::models::{Context, Listener, Listeners, ProgressListener};
use crate::error::{EngineError, EngineErrorCause, EngineErrorScope};
use crate::models::{Context, Listener, Listeners};
pub trait Kubernetes {
fn context(&self) -> &Context;
fn kind(&self) -> Kind;
fn id(&self) -> &str;
fn name(&self) -> &str;
fn name_with_id(&self) -> String {
format!("{} ({})", self.name(), self.id())
}
fn version(&self) -> &str;
fn region(&self) -> &str;
fn cloud_provider(&self) -> &dyn CloudProvider;
fn dns_provider(&self) -> &dyn DnsProvider;
fn is_valid(&self) -> Result<(), KubernetesError>;
fn is_valid(&self) -> Result<(), EngineError>;
fn add_listener(&mut self, listener: Listener);
fn listeners(&self) -> &Listeners;
fn resources(&self, environment: &Environment) -> Result<Resources, KubernetesError>;
fn on_create(&self) -> Result<(), KubernetesError>;
fn on_create_error(&self) -> Result<(), KubernetesError>;
fn on_upgrade(&self) -> Result<(), KubernetesError>;
fn on_upgrade_error(&self) -> Result<(), KubernetesError>;
fn on_downgrade(&self) -> Result<(), KubernetesError>;
fn on_downgrade_error(&self) -> Result<(), KubernetesError>;
fn on_delete(&self) -> Result<(), KubernetesError>;
fn on_delete_error(&self) -> Result<(), KubernetesError>;
fn deploy_environment(&self, environment: &Environment) -> Result<(), KubernetesError>;
fn deploy_environment_error(&self, environment: &Environment) -> Result<(), KubernetesError>;
fn pause_environment(&self, environment: &Environment) -> Result<(), KubernetesError>;
fn pause_environment_error(&self, environment: &Environment) -> Result<(), KubernetesError>;
fn delete_environment(&self, environment: &Environment) -> Result<(), KubernetesError>;
fn delete_environment_error(&self, environment: &Environment) -> Result<(), KubernetesError>;
fn resources(&self, environment: &Environment) -> Result<Resources, EngineError>;
fn on_create(&self) -> Result<(), EngineError>;
fn on_create_error(&self) -> Result<(), EngineError>;
fn on_upgrade(&self) -> Result<(), EngineError>;
fn on_upgrade_error(&self) -> Result<(), EngineError>;
fn on_downgrade(&self) -> Result<(), EngineError>;
fn on_downgrade_error(&self) -> Result<(), EngineError>;
fn on_delete(&self) -> Result<(), EngineError>;
fn on_delete_error(&self) -> Result<(), EngineError>;
fn deploy_environment(&self, environment: &Environment) -> Result<(), EngineError>;
fn deploy_environment_error(&self, environment: &Environment) -> Result<(), EngineError>;
fn pause_environment(&self, environment: &Environment) -> Result<(), EngineError>;
fn pause_environment_error(&self, environment: &Environment) -> Result<(), EngineError>;
fn delete_environment(&self, environment: &Environment) -> Result<(), EngineError>;
fn delete_environment_error(&self, environment: &Environment) -> Result<(), EngineError>;
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::Kubernetes(self.id().to_string(), self.name().to_string())
}
fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError {
EngineError::new(
cause,
self.engine_error_scope(),
self.context().execution_id(),
Some(message),
)
}
}
pub trait KubernetesNode {
@@ -64,91 +74,59 @@ pub struct Resources {
pub running_nodes: u16,
}
#[derive(Debug)]
pub enum KubernetesError {
Cmd(CmdError),
Io(std::io::Error),
Create(ExitStatus),
Deploy(ServiceError),
Pause(ServiceError),
Delete(ServiceError),
Error,
}
impl From<std::io::Error> for KubernetesError {
fn from(error: std::io::Error) -> Self {
KubernetesError::Io(error)
}
}
impl From<CmdError> for KubernetesError {
fn from(error: CmdError) -> Self {
KubernetesError::Cmd(error)
}
}
impl From<KubernetesError> for Option<ServiceError> {
fn from(item: KubernetesError) -> Self {
return match item {
KubernetesError::Deploy(e) | KubernetesError::Pause(e) | KubernetesError::Delete(e) => {
Option::from(e)
}
_ => None,
};
}
}
/// check that there is enough CPU and RAM, and pods resources
/// before starting to deploy stateful and stateless services
pub fn check_kubernetes_has_enough_resources_to_deploy_environment(
kubernetes: &dyn Kubernetes,
environment: &Environment,
) -> Result<(), KubernetesError> {
) -> Result<(), EngineError> {
let resources = kubernetes.resources(environment)?;
let required_resources = environment.required_resources();
let cause = EngineErrorCause::User("Contact your Organization administrator and consider to \
add one more node or upgrade your nodes configuration. If not possible, pause or delete unused environments");
if required_resources.cpu > resources.free_cpu
&& required_resources.ram_in_mib > resources.free_ram_in_mib
{
// not enough cpu and ram to deploy environment
return Err(KubernetesError::Deploy(ServiceError::NotEnoughResources(
format!(
"There is not enough CPU and RAM resources on the Kubernetes '{}' cluster. \
let message = format!(
"There is not enough CPU and RAM resources on the Kubernetes '{}' cluster. \
{} CPU and {}mib RAM requested. \
{} CPU and {}mib RAM available. \
Consider to add one more node or upgrade your nodes configuration.",
kubernetes.name(),
required_resources.cpu,
required_resources.ram_in_mib,
resources.free_cpu,
resources.free_ram_in_mib,
),
)));
{} CPU and {}mib RAM available.",
kubernetes.name(),
required_resources.cpu,
required_resources.ram_in_mib,
resources.free_cpu,
resources.free_ram_in_mib,
);
return Err(kubernetes.engine_error(cause, message));
} else if required_resources.cpu > resources.free_cpu {
// not enough cpu to deploy environment
return Err(KubernetesError::Deploy(ServiceError::NotEnoughResources(
format!(
"There is not enough free CPU on the Kubernetes '{}' cluster. \
let message = format!(
"There is not enough free CPU on the Kubernetes '{}' cluster. \
{} CPU requested. {} CPU available. \
Consider to add one more node or upgrade your nodes configuration.",
kubernetes.name(),
required_resources.cpu,
resources.free_cpu,
),
)));
kubernetes.name(),
required_resources.cpu,
resources.free_cpu,
);
return Err(kubernetes.engine_error(cause, message));
} else if required_resources.ram_in_mib > resources.free_ram_in_mib {
// not enough ram to deploy environment
return Err(KubernetesError::Deploy(ServiceError::NotEnoughResources(
format!(
"There is not enough free RAM on the Kubernetes cluster '{}'. \
let message = format!(
"There is not enough free RAM on the Kubernetes cluster '{}'. \
{}mib RAM requested. \
{}mib RAM available. \
Consider to add one more node or upgrade your nodes configuration.",
kubernetes.name(),
required_resources.ram_in_mib,
resources.free_ram_in_mib,
),
)));
kubernetes.name(),
required_resources.ram_in_mib,
resources.free_ram_in_mib,
);
return Err(kubernetes.engine_error(cause, message));
}
let mut required_pods = environment.stateless_services.len() as u16;
@@ -162,14 +140,14 @@ pub fn check_kubernetes_has_enough_resources_to_deploy_environment(
if required_pods > resources.free_pods {
// not enough free pods on the cluster
return Err(KubernetesError::Deploy(ServiceError::NotEnoughResources(
format!(
"There is not enough free Pods ({} required) on the Kubernetes cluster '{}'. \
let message = format!(
"There is not enough free Pods ({} required) on the Kubernetes cluster '{}'. \
Consider to add one more node or upgrade your nodes configuration.",
required_pods,
kubernetes.name(),
),
)));
required_pods,
kubernetes.name(),
);
return Err(kubernetes.engine_error(cause, message));
}
Ok(())

View File

@@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::cloud_provider::environment::Environment;
use crate::cloud_provider::kubernetes::Kubernetes;
use crate::error::{EngineError, EngineErrorCause, EngineErrorScope};
use crate::models::{Context, Listener, ProgressListener};
pub mod aws;
@@ -21,50 +22,26 @@ pub trait CloudProvider {
fn id(&self) -> &str;
fn organization_id(&self) -> &str;
fn name(&self) -> &str;
fn is_valid(&self) -> Result<(), CloudProviderError>;
fn name_with_id(&self) -> String {
format!("{} ({})", self.name(), self.id())
}
fn is_valid(&self) -> Result<(), EngineError>;
fn add_listener(&mut self, listener: Listener);
fn terraform_state_credentials(&self) -> &TerraformStateCredentials;
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::CloudProvider(self.id().to_string(), self.name().to_string())
}
fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError {
EngineError::new(
cause,
self.engine_error_scope(),
self.context().execution_id(),
Some(message),
)
}
fn as_any(&self) -> &dyn Any;
}
#[derive(Debug)]
pub enum CloudProviderError {
Credentials,
Error(Box<dyn std::error::Error>),
Unknown,
}
impl From<Box<dyn std::error::Error>> for CloudProviderError {
fn from(error: Box<dyn std::error::Error>) -> Self {
CloudProviderError::Error(error)
}
}
impl<E> From<RusotoError<E>> for CloudProviderError {
fn from(error: RusotoError<E>) -> Self {
match error {
RusotoError::Credentials(_) => CloudProviderError::Credentials,
RusotoError::Service(_) => CloudProviderError::Unknown,
RusotoError::HttpDispatch(_) => CloudProviderError::Unknown,
RusotoError::Validation(_) => CloudProviderError::Unknown,
RusotoError::ParseError(_) => CloudProviderError::Unknown,
RusotoError::Unknown(e) => {
if e.status == 403 {
CloudProviderError::Credentials
} else {
CloudProviderError::Unknown
}
}
RusotoError::Blocking => CloudProviderError::Unknown,
}
}
}
#[derive(Debug)]
pub enum DeployError {
Error,
}
#[derive(Serialize, Deserialize, Clone)]
pub enum Kind {
AWS,

View File

@@ -1,4 +1,3 @@
use std::io::Error;
use std::net::TcpStream;
use std::process::id;
@@ -8,15 +7,17 @@ use crate::build_platform::Image;
use crate::cloud_provider::environment::Environment;
use crate::cloud_provider::kubernetes::Kubernetes;
use crate::cloud_provider::DeploymentTarget;
use crate::cmd::utilities::CmdError;
use crate::error::{EngineError, EngineErrorCause, EngineErrorScope};
use crate::models::{Context, ProgressScope};
use crate::transaction::CommitError;
pub trait Service {
fn context(&self) -> &Context;
fn service_type(&self) -> ServiceType;
fn id(&self) -> &str;
fn name(&self) -> &str;
fn name_with_id(&self) -> String {
format!("{} ({})", self.name(), self.id())
}
fn version(&self) -> &str;
fn action(&self) -> &Action;
fn private_port(&self) -> Option<u16>;
@@ -35,13 +36,21 @@ pub trait Service {
}
}
fn is_valid(&self) -> Result<(), ServiceError> {
fn is_valid(&self) -> Result<(), EngineError> {
let binaries = ["kubectl", "helm", "terraform", "aws-iam-authenticator"];
for binary in binaries.iter() {
if !crate::cmd::utilities::does_binary_exist(binary) {
let err = format!("{} binary not found", binary);
return Err(ServiceError::Unexpected(err));
let cause = EngineErrorCause::Internal;
return Err(EngineError::new(
EngineErrorCause::Internal,
EngineErrorScope::Engine,
self.id(),
Some(err),
));
}
}
@@ -93,7 +102,7 @@ pub trait Service {
}
pub trait StatelessService: Service + Create + Pause + Delete {
fn exec_action(&self, deployment_target: &DeploymentTarget) -> Result<(), ServiceError> {
fn exec_action(&self, deployment_target: &DeploymentTarget) -> Result<(), EngineError> {
match self.action() {
crate::cloud_provider::service::Action::Create => self.on_create(deployment_target),
crate::cloud_provider::service::Action::Delete => self.on_delete(deployment_target),
@@ -106,7 +115,7 @@ pub trait StatelessService: Service + Create + Pause + Delete {
pub trait StatefulService:
Service + Create + Pause + Delete + Backup + Clone + Upgrade + Downgrade
{
fn exec_action(&self, deployment_target: &DeploymentTarget) -> Result<(), ServiceError> {
fn exec_action(&self, deployment_target: &DeploymentTarget) -> Result<(), EngineError> {
match self.action() {
crate::cloud_provider::service::Action::Create => self.on_create(deployment_target),
crate::cloud_provider::service::Action::Delete => self.on_delete(deployment_target),
@@ -119,59 +128,109 @@ pub trait StatefulService:
pub trait Application: StatelessService {
fn image(&self) -> &Image;
fn set_image(&mut self, image: Image);
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::Application(self.id().to_string(), self.name().to_string())
}
fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError {
EngineError::new(
cause,
self.engine_error_scope(),
self.context().execution_id(),
Some(message),
)
}
}
pub trait ExternalService: Application {}
pub trait ExternalService: StatelessService {
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::ExternalService(self.id().to_string(), self.name().to_string())
}
fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError {
EngineError::new(
cause,
self.engine_error_scope(),
self.context().execution_id(),
Some(message),
)
}
}
pub trait Router: StatelessService {
fn check_domains(&self) -> Result<(), ServiceError>;
fn check_domains(&self) -> Result<(), EngineError>;
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::Router(self.id().to_string(), self.name().to_string())
}
fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError {
EngineError::new(
cause,
self.engine_error_scope(),
self.context().execution_id(),
Some(message),
)
}
}
pub trait Database: StatefulService {}
pub trait Database: StatefulService {
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::Database(
self.id().to_string(),
self.service_type().name().to_string(),
self.name().to_string(),
)
}
fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError {
EngineError::new(
cause,
self.engine_error_scope(),
self.context().execution_id(),
Some(message),
)
}
}
pub trait Create {
fn on_create(&self, target: &DeploymentTarget) -> Result<(), ServiceError>;
fn on_create_check(&self) -> Result<(), ServiceError>;
fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError>;
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError>;
fn on_create_check(&self) -> Result<(), EngineError>;
fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError>;
}
pub trait Pause {
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), ServiceError>;
fn on_pause_check(&self) -> Result<(), ServiceError>;
fn on_pause_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError>;
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError>;
fn on_pause_check(&self) -> Result<(), EngineError>;
fn on_pause_error(&self, target: &DeploymentTarget) -> Result<(), EngineError>;
}
pub trait Delete {
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), ServiceError>;
fn on_delete_check(&self) -> Result<(), ServiceError>;
fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError>;
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError>;
fn on_delete_check(&self) -> Result<(), EngineError>;
fn on_delete_error(&self, target: &DeploymentTarget) -> Result<(), EngineError>;
}
pub trait Backup {
fn on_backup(&self, target: &DeploymentTarget) -> Result<(), ServiceError>;
fn on_backup_check(&self) -> Result<(), ServiceError>;
fn on_backup_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError>;
fn on_restore(&self, target: &DeploymentTarget) -> Result<(), ServiceError>;
fn on_restore_check(&self) -> Result<(), ServiceError>;
fn on_restore_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError>;
fn on_backup(&self, target: &DeploymentTarget) -> Result<(), EngineError>;
fn on_backup_check(&self) -> Result<(), EngineError>;
fn on_backup_error(&self, target: &DeploymentTarget) -> Result<(), EngineError>;
fn on_restore(&self, target: &DeploymentTarget) -> Result<(), EngineError>;
fn on_restore_check(&self) -> Result<(), EngineError>;
fn on_restore_error(&self, target: &DeploymentTarget) -> Result<(), EngineError>;
}
pub trait Clone {
fn on_clone(&self, target: &DeploymentTarget) -> Result<(), ServiceError>;
fn on_clone_check(&self) -> Result<(), ServiceError>;
fn on_clone_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError>;
fn on_clone(&self, target: &DeploymentTarget) -> Result<(), EngineError>;
fn on_clone_check(&self) -> Result<(), EngineError>;
fn on_clone_error(&self, target: &DeploymentTarget) -> Result<(), EngineError>;
}
pub trait Upgrade {
fn on_upgrade(&self, target: &DeploymentTarget) -> Result<(), ServiceError>;
fn on_upgrade_check(&self) -> Result<(), ServiceError>;
fn on_upgrade_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError>;
fn on_upgrade(&self, target: &DeploymentTarget) -> Result<(), EngineError>;
fn on_upgrade_check(&self) -> Result<(), EngineError>;
fn on_upgrade_error(&self, target: &DeploymentTarget) -> Result<(), EngineError>;
}
pub trait Downgrade {
fn on_downgrade(&self, target: &DeploymentTarget) -> Result<(), ServiceError>;
fn on_downgrade_check(&self) -> Result<(), ServiceError>;
fn on_downgrade_error(&self, target: &DeploymentTarget) -> Result<(), ServiceError>;
fn on_downgrade(&self, target: &DeploymentTarget) -> Result<(), EngineError>;
fn on_downgrade_check(&self) -> Result<(), EngineError>;
fn on_downgrade_error(&self, target: &DeploymentTarget) -> Result<(), EngineError>;
}
#[derive(Clone, Eq, PartialEq, Hash)]
@@ -212,44 +271,12 @@ impl<'a> ServiceType<'a> {
match self {
ServiceType::Application => "Application",
ServiceType::ExternalService => "ExternalService",
ServiceType::Database(_) => "Database",
ServiceType::Database(db_type) => match db_type {
DatabaseType::PostgreSQL(_) => "PostgreSQL database",
DatabaseType::MongoDB(_) => "MongoDB database",
DatabaseType::MySQL(_) => "MySQL database",
},
ServiceType::Router => "Router",
}
}
}
#[derive(Debug)]
pub enum ServiceError {
OnCreateFailed,
CheckFailed,
Cmd(CmdError),
Io(Error),
NotEnoughResources(String),
Unexpected(String),
}
impl From<std::io::Error> for ServiceError {
fn from(err: Error) -> Self {
ServiceError::Io(err)
}
}
impl From<CmdError> for ServiceError {
fn from(err: CmdError) -> Self {
ServiceError::Cmd(err)
}
}
impl From<CommitError> for Option<ServiceError> {
fn from(err: CommitError) -> Self {
return match err {
CommitError::DeleteEnvironment(e)
| CommitError::PauseEnvironment(e)
| CommitError::DeployEnvironment(e)
| CommitError::DeleteKubernetes(e)
| CommitError::CreateKubernetes(e) => Option::from(e),
CommitError::NotValidService(e) => Option::Some(e),
_ => None,
};
}
}

View File

@@ -12,8 +12,9 @@ use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::cmd::structs::{Helm, HelmHistoryRow};
use crate::cmd::utilities::{exec_with_envs_and_output, CmdError};
use crate::cmd::utilities::exec_with_envs_and_output;
use crate::constants::{KUBECONFIG, TF_PLUGIN_CACHE_DIR};
use crate::error::{SimpleError, SimpleErrorKind};
pub fn helm_exec_with_upgrade_history<P>(
kubernetes_config: P,
@@ -21,7 +22,7 @@ pub fn helm_exec_with_upgrade_history<P>(
release_name: &str,
chart_root_dir: P,
envs: Vec<(&str, &str)>,
) -> Result<Option<HelmHistoryRow>, CmdError>
) -> Result<Option<HelmHistoryRow>, SimpleError>
where
P: AsRef<Path>,
{
@@ -63,7 +64,7 @@ pub fn helm_exec_upgrade<P>(
release_name: &str,
chart_root_dir: P,
envs: Vec<(&str, &str)>,
) -> Result<(), CmdError>
) -> Result<(), SimpleError>
where
P: AsRef<Path>,
{
@@ -99,7 +100,7 @@ pub fn helm_exec_uninstall<P>(
namespace: &str,
release_name: &str,
envs: Vec<(&str, &str)>,
) -> Result<(), CmdError>
) -> Result<(), SimpleError>
where
P: AsRef<Path>,
{
@@ -129,7 +130,7 @@ pub fn helm_exec_history<P>(
namespace: &str,
release_name: &str,
envs: Vec<(&str, &str)>,
) -> Result<Vec<HelmHistoryRow>, CmdError>
) -> Result<Vec<HelmHistoryRow>, SimpleError>
where
P: AsRef<Path>,
{
@@ -178,7 +179,7 @@ pub fn helm_uninstall_list<P>(
kubernetes_config: P,
helmlist: Vec<String>,
envs: Vec<(&str, &str)>,
) -> Result<String, CmdError>
) -> Result<String, SimpleError>
where
P: AsRef<Path>,
{
@@ -217,7 +218,7 @@ pub fn helm_exec_upgrade_with_override_file<P>(
chart_root_dir: P,
override_file: &str,
envs: Vec<(&str, &str)>,
) -> Result<(), CmdError>
) -> Result<(), SimpleError>
where
P: AsRef<Path>,
{
@@ -259,7 +260,7 @@ pub fn helm_exec_with_upgrade_history_with_override<P>(
chart_root_dir: P,
override_file: &str,
envs: Vec<(&str, &str)>,
) -> Result<Option<HelmHistoryRow>, CmdError>
) -> Result<Option<HelmHistoryRow>, SimpleError>
where
P: AsRef<Path>,
{
@@ -296,7 +297,10 @@ where
})
}
pub fn helm_list<P>(kubernetes_config: P, envs: Vec<(&str, &str)>) -> Result<Vec<String>, CmdError>
pub fn helm_list<P>(
kubernetes_config: P,
envs: Vec<(&str, &str)>,
) -> Result<Vec<String>, SimpleError>
where
P: AsRef<Path>,
{
@@ -330,14 +334,15 @@ where
}
}
Err(e) => {
error!("Error while deserializing all helms names {}", e);
return Err(CmdError::Io(Error::new(std::io::ErrorKind::InvalidData, e)));
let message = format!("Error while deserializing all helms names {}", e);
error!("{}", message.as_str());
return Err(SimpleError::new(SimpleErrorKind::Other, Some(message)));
}
}
Ok(helms_name)
}
pub fn helm_exec(args: Vec<&str>, envs: Vec<(&str, &str)>) -> Result<(), CmdError> {
pub fn helm_exec(args: Vec<&str>, envs: Vec<(&str, &str)>) -> Result<(), SimpleError> {
helm_exec_with_output(
args,
envs,
@@ -355,7 +360,7 @@ pub fn helm_exec_with_output<F, X>(
envs: Vec<(&str, &str)>,
stdout_output: F,
stderr_output: X,
) -> Result<(), CmdError>
) -> Result<(), SimpleError>
where
F: FnMut(Result<String, Error>),
X: FnMut(Result<String, Error>),
@@ -373,7 +378,7 @@ pub fn kubectl_exec_with_output<F, X>(
envs: Vec<(&str, &str)>,
stdout_output: F,
stderr_output: X,
) -> Result<(), CmdError>
) -> Result<(), SimpleError>
where
F: FnMut(Result<String, Error>),
X: FnMut(Result<String, Error>),

View File

@@ -15,15 +15,16 @@ use crate::cmd::structs::{
Item, KubernetesJob, KubernetesList, KubernetesNode, KubernetesPod, KubernetesPodStatusPhase,
KubernetesService,
};
use crate::cmd::utilities::{exec_with_envs_and_output, CmdError};
use crate::cmd::utilities::exec_with_envs_and_output;
use crate::constants::{KUBECONFIG, TF_PLUGIN_CACHE_DIR};
use crate::error::{SimpleError, SimpleErrorKind};
pub fn kubectl_exec_with_output<F, X>(
args: Vec<&str>,
envs: Vec<(&str, &str)>,
stdout_output: F,
stderr_output: X,
) -> Result<(), CmdError>
) -> Result<(), SimpleError>
where
F: FnMut(Result<String, Error>),
X: FnMut(Result<String, Error>),
@@ -41,7 +42,7 @@ pub fn kubectl_exec_get_external_ingress_hostname<P>(
namespace: &str,
selector: &str,
envs: Vec<(&str, &str)>,
) -> Result<Option<String>, CmdError>
) -> Result<Option<String>, SimpleError>
where
P: AsRef<Path>,
{
@@ -74,10 +75,10 @@ where
Err(err) => {
error!("{:?}", err);
error!("{}", output_string.as_str());
return Err(CmdError::Io(Error::new(
std::io::ErrorKind::InvalidData,
output_string,
)));
return Err(SimpleError::new(
SimpleErrorKind::Other,
Some(output_string),
));
}
};
@@ -115,7 +116,7 @@ pub fn kubectl_exec_is_pod_ready_with_retry<P>(
namespace: &str,
selector: &str,
envs: Vec<(&str, &str)>,
) -> Result<Option<bool>, CmdError>
) -> Result<Option<bool>, SimpleError>
where
P: AsRef<Path>,
{
@@ -148,7 +149,7 @@ where
total_delay: _,
tries: _,
} => Ok(Some(false)),
retry::Error::Internal(err) => Err(CmdError::Unexpected(err)),
retry::Error::Internal(err) => Err(SimpleError::new(SimpleErrorKind::Other, Some(err))),
},
Ok(_) => Ok(Some(true)),
}
@@ -159,7 +160,7 @@ pub fn kubectl_exec_is_pod_ready<P>(
namespace: &str,
selector: &str,
envs: Vec<(&str, &str)>,
) -> Result<Option<bool>, CmdError>
) -> Result<Option<bool>, SimpleError>
where
P: AsRef<Path>,
{
@@ -189,10 +190,10 @@ where
Err(err) => {
error!("{:?}", err);
error!("{}", output_string.as_str());
return Err(CmdError::Io(Error::new(
std::io::ErrorKind::InvalidData,
output_string,
)));
return Err(SimpleError::new(
SimpleErrorKind::Other,
Some(output_string),
));
}
};
@@ -223,7 +224,7 @@ pub fn kubectl_exec_is_job_ready_with_retry<P>(
namespace: &str,
job_name: &str,
envs: Vec<(&str, &str)>,
) -> Result<Option<bool>, CmdError>
) -> Result<Option<bool>, SimpleError>
where
P: AsRef<Path>,
{
@@ -256,7 +257,7 @@ where
total_delay: _,
tries: _,
} => Ok(Some(false)),
retry::Error::Internal(err) => Err(CmdError::Unexpected(err)),
retry::Error::Internal(err) => Err(SimpleError::new(SimpleErrorKind::Other, Some(err))),
},
Ok(_) => Ok(Some(true)),
}
@@ -267,7 +268,7 @@ pub fn kubectl_exec_is_job_ready<P>(
namespace: &str,
job_name: &str,
envs: Vec<(&str, &str)>,
) -> Result<Option<bool>, CmdError>
) -> Result<Option<bool>, SimpleError>
where
P: AsRef<Path>,
{
@@ -296,10 +297,10 @@ where
Err(err) => {
error!("{:?}", err);
error!("{}", output_string.as_str());
return Err(CmdError::Io(Error::new(
std::io::ErrorKind::InvalidData,
output_string,
)));
return Err(SimpleError::new(
SimpleErrorKind::Other,
Some(output_string),
));
}
};
@@ -314,7 +315,7 @@ pub fn kubectl_exec_create_namespace<P>(
kubernetes_config: P,
namespace: &str,
envs: Vec<(&str, &str)>,
) -> Result<(), CmdError>
) -> Result<(), SimpleError>
where
P: AsRef<Path>,
{
@@ -344,7 +345,7 @@ pub fn create_sample_secret_terraform_in_namespace<P>(
kubernetes_config: P,
namespace_to_override: &str,
envs: &Vec<(&str, &str)>,
) -> Result<String, CmdError>
) -> Result<String, SimpleError>
where
P: AsRef<Path>,
{
@@ -378,7 +379,7 @@ pub fn does_contain_terraform_tfstate<P>(
kubernetes_config: P,
namespace: &str,
envs: &Vec<(&str, &str)>,
) -> Result<bool, CmdError>
) -> Result<bool, SimpleError>
where
P: AsRef<Path>,
{
@@ -409,7 +410,7 @@ where
pub fn kubectl_exec_get_all_namespaces<P>(
kubernetes_config: P,
envs: Vec<(&str, &str)>,
) -> Result<Vec<String>, Error>
) -> Result<Vec<String>, SimpleError>
where
P: AsRef<Path>,
{
@@ -441,11 +442,15 @@ where
}
}
Err(e) => {
error!("While deserializing Kubernetes namespaces names {}", e);
return Err(Error::from(CmdError::Io(Error::new(
std::io::ErrorKind::InvalidData,
output_string,
))));
error!(
"Error while deserializing Kubernetes namespaces names {}",
e
);
return Err(SimpleError::new(
SimpleErrorKind::Other,
Some(output_string),
));
}
};
Ok(to_return)
@@ -455,17 +460,17 @@ pub fn kubectl_exec_delete_namespace<P>(
kubernetes_config: P,
namespace: &str,
envs: Vec<(&str, &str)>,
) -> Result<(), CmdError>
) -> Result<(), SimpleError>
where
P: AsRef<Path>,
{
match does_contain_terraform_tfstate(&kubernetes_config, &namespace, &envs) {
Ok(exist) => match exist {
true => {
return Err(CmdError::Io(Error::new(
std::io::ErrorKind::Other,
"Namespace contains terraform tfstates in secret, can't delete it !",
)));
return Err(SimpleError::new(
SimpleErrorKind::Other,
Some("Namespace contains terraform tfstates in secret, can't delete it !"),
));
}
false => info!(
"Namespace {} doesn't contain any tfstates, able to delete it",
@@ -474,7 +479,7 @@ where
},
Err(e) => warn!(
"Unable to execute describe on secrets: {}. it may not exist anymore?",
e
e.message.unwrap_or("".into())
),
};
@@ -502,7 +507,7 @@ pub fn kubectl_exec_delete_secret<P>(
kubernetes_config: P,
secret: &str,
envs: Vec<(&str, &str)>,
) -> Result<(), CmdError>
) -> Result<(), SimpleError>
where
P: AsRef<Path>,
{
@@ -531,7 +536,7 @@ pub fn kubectl_exec_logs<P>(
namespace: &str,
selector: &str,
envs: Vec<(&str, &str)>,
) -> Result<String, CmdError>
) -> Result<String, SimpleError>
where
P: AsRef<Path>,
{
@@ -561,7 +566,7 @@ pub fn kubectl_exec_describe_pod<P>(
namespace: &str,
selector: &str,
envs: Vec<(&str, &str)>,
) -> Result<String, CmdError>
) -> Result<String, SimpleError>
where
P: AsRef<Path>,
{
@@ -589,7 +594,7 @@ where
pub fn kubectl_exec_get_node<P>(
kubernetes_config: P,
envs: Vec<(&str, &str)>,
) -> Result<KubernetesList<KubernetesNode>, CmdError>
) -> Result<KubernetesList<KubernetesNode>, SimpleError>
where
P: AsRef<Path>,
{
@@ -619,10 +624,10 @@ where
Err(err) => {
error!("{:?}", err);
error!("{}", output_string.as_str());
return Err(CmdError::Io(Error::new(
std::io::ErrorKind::InvalidData,
output_string,
)));
return Err(SimpleError::new(
SimpleErrorKind::Other,
Some(output_string),
));
}
};

View File

@@ -11,13 +11,14 @@ use retry::OperationResult;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::cmd::utilities::{exec_with_envs_and_output, CmdError};
use crate::cmd::utilities::exec_with_envs_and_output;
use crate::constants::{KUBECONFIG, TF_PLUGIN_CACHE_DIR};
use crate::error::SimpleError;
fn terraform_exec_with_init_validate(
root_dir: &str,
first_time_init_terraform: bool,
) -> Result<(), CmdError> {
) -> Result<(), SimpleError> {
// terraform init
let init_args = if first_time_init_terraform {
vec!["init"]
@@ -37,7 +38,7 @@ fn terraform_exec_with_init_validate(
fn terraform_exec_with_init_validate_plan(
root_dir: &str,
first_time_init_terraform: bool,
) -> Result<(), CmdError> {
) -> Result<(), SimpleError> {
// terraform init
let init_args = if first_time_init_terraform {
vec!["init"]
@@ -60,7 +61,7 @@ fn terraform_exec_with_init_validate_plan(
pub fn terraform_exec_with_init_validate_plan_apply(
root_dir: &str,
first_time_init_terraform: bool,
) -> Result<(), CmdError> {
) -> Result<(), SimpleError> {
// terraform init and plan
terraform_exec_with_init_validate_plan(root_dir, first_time_init_terraform);
@@ -70,7 +71,7 @@ pub fn terraform_exec_with_init_validate_plan_apply(
Ok(())
}
pub fn terraform_exec_with_init_validate_destroy(root_dir: &str) -> Result<(), CmdError> {
pub fn terraform_exec_with_init_validate_destroy(root_dir: &str) -> Result<(), SimpleError> {
// terraform init and plan
terraform_exec_with_init_validate(root_dir, false);
@@ -78,11 +79,11 @@ pub fn terraform_exec_with_init_validate_destroy(root_dir: &str) -> Result<(), C
terraform_exec(root_dir, vec!["destroy", "-auto-approve"])
}
pub fn terraform_exec(root_dir: &str, args: Vec<&str>) -> Result<(), CmdError> {
pub fn terraform_exec(root_dir: &str, args: Vec<&str>) -> Result<(), SimpleError> {
let home_dir = home_dir().expect("Could not find $HOME");
let tf_plugin_cache_dir = format!("{}/.terraform.d/plugin-cache", home_dir.to_str().unwrap());
match exec_with_envs_and_output(
exec_with_envs_and_output(
format!("{} terraform", root_dir).as_str(),
args,
vec![(TF_PLUGIN_CACHE_DIR, tf_plugin_cache_dir.as_str())],
@@ -92,10 +93,5 @@ pub fn terraform_exec(root_dir: &str, args: Vec<&str>) -> Result<(), CmdError> {
|line: Result<String, std::io::Error>| {
error!("{}", line.unwrap());
},
) {
Err(err) => return Err(err),
Ok(out) => Ok(out),
};
Ok(())
)
}

View File

@@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::constants::{KUBECONFIG, TF_PLUGIN_CACHE_DIR};
use crate::error::{SimpleError, SimpleErrorKind};
fn command<P>(binary: P, args: Vec<&str>, envs: Option<Vec<(&str, &str)>>) -> Command
where
@@ -53,7 +54,7 @@ where
cmd
}
pub fn exec<P>(binary: P, args: Vec<&str>) -> Result<(), CmdError>
pub fn exec<P>(binary: P, args: Vec<&str>) -> Result<(), SimpleError>
where
P: AsRef<Path>,
{
@@ -62,21 +63,24 @@ where
let exit_status = match command(binary, args, None).spawn().unwrap().wait() {
Ok(x) => x,
Err(err) => return Err(CmdError::Io(err)),
Err(err) => return Err(SimpleError::from(err)),
};
if exit_status.success() {
return Ok(());
}
Err(CmdError::Exec(exit_status))
Err(SimpleError::new::<&str>(
SimpleErrorKind::Command(exit_status),
None,
))
}
pub fn exec_with_envs<P>(
binary: P,
args: Vec<&str>,
envs: Vec<(&str, &str)>,
) -> Result<(), CmdError>
) -> Result<(), SimpleError>
where
P: AsRef<Path>,
{
@@ -85,14 +89,17 @@ where
let exit_status = match command(binary, args, Some(envs)).spawn().unwrap().wait() {
Ok(x) => x,
Err(err) => return Err(CmdError::Io(err)),
Err(err) => return Err(SimpleError::from(err)),
};
if exit_status.success() {
return Ok(());
}
Err(CmdError::Exec(exit_status))
Err(SimpleError::new::<String>(
SimpleErrorKind::Command(exit_status),
None,
))
}
fn _with_output<F, X>(mut child: Child, mut stdout_output: F, mut stderr_output: X) -> Child
@@ -118,7 +125,7 @@ pub fn exec_with_output<P, F, X>(
args: Vec<&str>,
stdout_output: F,
stderr_output: X,
) -> Result<(), CmdError>
) -> Result<(), SimpleError>
where
P: AsRef<Path>,
F: FnMut(Result<String, Error>),
@@ -135,14 +142,17 @@ where
let exit_status = match child.wait() {
Ok(x) => x,
Err(err) => return Err(CmdError::Io(err)),
Err(err) => return Err(SimpleError::from(err)),
};
if exit_status.success() {
return Ok(());
}
Err(CmdError::Exec(exit_status))
Err(SimpleError::new(
SimpleErrorKind::Command(exit_status),
Some(command_string),
))
}
pub fn exec_with_envs_and_output<P, F, X>(
@@ -151,7 +161,7 @@ pub fn exec_with_envs_and_output<P, F, X>(
envs: Vec<(&str, &str)>,
stdout_output: F,
stderr_output: X,
) -> Result<(), CmdError>
) -> Result<(), SimpleError>
where
P: AsRef<Path>,
F: FnMut(Result<String, Error>),
@@ -168,14 +178,17 @@ where
let exit_status = match child.wait() {
Ok(x) => x,
Err(err) => return Err(CmdError::Io(err)),
Err(err) => return Err(SimpleError::from(err)),
};
if exit_status.success() {
return Ok(());
}
Err(CmdError::Exec(exit_status))
Err(SimpleError::new(
SimpleErrorKind::Command(exit_status),
Some(command_string),
))
}
// return the output of "binary_name" --version
@@ -238,35 +251,3 @@ where
args.join(" ")
)
}
#[derive(Debug)]
pub enum CmdError {
Exec(ExitStatus),
Io(Error),
Unexpected(String),
}
impl Display for CmdError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let s = match self {
CmdError::Exec(status) => format!("CmdError: Exec({})", status),
CmdError::Io(io) => format!("CmdError: IO: {}", io),
CmdError::Unexpected(s) => format!("CmdError: Unexpected: {}", s),
};
write!(f, "{}", s)
}
}
impl std::error::Error for CmdError {}
impl From<std::io::Error> for CmdError {
fn from(err: Error) -> Self {
CmdError::Io(err)
}
}
impl From<CmdError> for std::io::Error {
fn from(e: CmdError) -> Self {
std::io::Error::new(std::io::ErrorKind::Other, e)
}
}

View File

@@ -2,10 +2,8 @@ use std::rc::Rc;
use crate::build_platform::Image;
use crate::cmd;
use crate::cmd::utilities::CmdError;
use crate::container_registry::{
ContainerRegistry, ContainerRegistryError, Kind, PushError, PushResult,
};
use crate::container_registry::{ContainerRegistry, EngineError, Kind, PushResult};
use crate::error::EngineErrorCause;
use crate::models::{Context, Listener, Listeners, ProgressListener};
pub struct DockerHub {
@@ -47,7 +45,7 @@ impl ContainerRegistry for DockerHub {
self.name.as_str()
}
fn is_valid(&self) -> Result<(), ContainerRegistryError> {
fn is_valid(&self) -> Result<(), EngineError> {
// check the version of docker and print it as info
let mut output_from_cmd = String::new();
cmd::utilities::exec_with_output(
@@ -70,19 +68,19 @@ impl ContainerRegistry for DockerHub {
self.listeners.push(listener);
}
fn on_create(&self) -> Result<(), ContainerRegistryError> {
fn on_create(&self) -> Result<(), EngineError> {
Ok(())
}
fn on_create_error(&self) -> Result<(), ContainerRegistryError> {
fn on_create_error(&self) -> Result<(), EngineError> {
Ok(())
}
fn on_delete(&self) -> Result<(), ContainerRegistryError> {
fn on_delete(&self) -> Result<(), EngineError> {
Ok(())
}
fn on_delete_error(&self) -> Result<(), ContainerRegistryError> {
fn on_delete_error(&self) -> Result<(), EngineError> {
Ok(())
}
@@ -104,20 +102,13 @@ impl ContainerRegistry for DockerHub {
],
envs.clone(),
) {
Err(err) => match err {
CmdError::Exec(exit_status) => {
error!("Cannot login into dockerhub");
return false;
}
CmdError::Io(err) => {
error!("IO error on dockerhub login: {}", err);
return false;
}
CmdError::Unexpected(err) => {
error!("Unexpected error on dockerhub login: {}", err);
return false;
}
},
Err(err) => {
if let Some(message) = err.message {
error!("{}", message);
};
return false;
}
_ => {}
};
@@ -130,23 +121,24 @@ impl ContainerRegistry for DockerHub {
let mut exist_stdoud: bool = false;
let mut exist_stderr: bool = true;
// TODO Change this by using curl lib
cmd::utilities::exec_with_envs_and_output(
"curl",
vec!["--silent", "-f", "-lSL", &curl_path],
envs.clone(),
|r_out| match r_out {
Ok(s) => exist_stdoud = true,
Ok(_) => exist_stdoud = true,
Err(e) => error!("Error while getting stdout from curl {}", e),
},
|r_err| match r_err {
Ok(s) => exist_stderr = true,
Ok(_) => exist_stderr = true,
Err(e) => error!("Error while getting stderr from curl {}", e),
},
);
exist_stdoud
}
fn push(&self, image: &Image, force_push: bool) -> Result<PushResult, PushError> {
fn push(&self, image: &Image, force_push: bool) -> Result<PushResult, EngineError> {
let envs = match self.context.docker_tcp_socket() {
Some(tcp_socket) => vec![("DOCKER_HOST", tcp_socket.as_str())],
None => vec![],
@@ -163,11 +155,14 @@ impl ContainerRegistry for DockerHub {
],
envs.clone(),
) {
Err(err) => match err {
CmdError::Exec(exit_status) => return Err(PushError::CredentialsError),
CmdError::Io(err) => return Err(PushError::IoError(err)),
CmdError::Unexpected(err) => return Err(PushError::Unknown(err)),
},
Err(_) => {
return Err(
self.engine_error(
EngineErrorCause::User("Your DockerHub account seems to be no longer valid (bad Credentials). \
Please contact your Organization administrator to fix or change the Credentials."),
format!("failed to login to DockerHub {}", self.name_with_id()))
);
}
_ => {}
};
@@ -181,20 +176,30 @@ impl ContainerRegistry for DockerHub {
],
envs.clone(),
) {
Err(err) => match err {
CmdError::Exec(exit_status) => return Err(PushError::ImageTagFailed),
CmdError::Io(err) => return Err(PushError::IoError(err)),
CmdError::Unexpected(err) => return Err(PushError::Unknown(err)),
},
Err(_) => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"failed to tag image ({}) {:?}",
image.name_with_tag(),
image,
),
));
}
_ => {}
};
match cmd::utilities::exec_with_envs("docker", vec!["push", dest.as_str()], envs) {
Err(err) => match err {
CmdError::Exec(exit_status) => return Err(PushError::ImagePushFailed),
CmdError::Io(err) => return Err(PushError::IoError(err)),
CmdError::Unexpected(err) => return Err(PushError::Unknown(err)),
},
Err(_) => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"failed to push image {:?} into DockerHub {}",
image,
self.name_with_id(),
),
));
}
_ => {}
};
@@ -204,7 +209,7 @@ impl ContainerRegistry for DockerHub {
Ok(PushResult { image })
}
fn push_error(&self, _image: &Image) -> Result<PushResult, PushError> {
fn push_error(&self, _image: &Image) -> Result<PushResult, EngineError> {
unimplemented!()
}
}

View File

@@ -6,10 +6,8 @@ use digitalocean::DigitalOcean;
use crate::build_platform::Image;
use crate::cmd;
use crate::cmd::utilities::CmdError;
use crate::container_registry::{
ContainerRegistry, ContainerRegistryError, Kind, PushError, PushResult,
};
use crate::container_registry::{ContainerRegistry, EngineError, Kind, PushResult};
use crate::error::{EngineErrorCause, EngineErrorScope};
use crate::models::{Context, Listener, ProgressListener};
// TODO : use --output json
@@ -33,7 +31,7 @@ impl DOCR {
DigitalOcean::new(self.api_key.as_str()).unwrap()
}
pub fn create_repository(&self, _image: &Image) -> Result<(), ContainerRegistryError> {
pub fn create_repository(&self, _image: &Image) -> Result<(), EngineError> {
match cmd::utilities::exec(
"doctl",
vec![
@@ -44,35 +42,47 @@ impl DOCR {
self.api_key.as_str(),
],
) {
Err(err) => match err {
CmdError::Exec(_exit_status) => return Err(ContainerRegistryError::Unknown),
CmdError::Io(err) => return Err(ContainerRegistryError::Unknown),
CmdError::Unexpected(err) => return Err(ContainerRegistryError::Unknown),
},
Err(_) => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!("failed to create DOCR {}", self.registry_name.as_str()),
));
}
_ => {}
};
Ok(())
}
pub fn push_image(&self, dest: String, image: &Image) -> Result<PushResult, PushError> {
pub fn push_image(&self, dest: String, image: &Image) -> Result<PushResult, EngineError> {
match cmd::utilities::exec(
"docker",
vec!["tag", image.name_with_tag().as_str(), dest.as_str()],
) {
Err(err) => match err {
CmdError::Exec(_exit_status) => return Err(PushError::ImageTagFailed),
CmdError::Io(err) => return Err(PushError::IoError(err)),
CmdError::Unexpected(err) => return Err(PushError::Unknown(err)),
},
Err(_) => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"failed to tag image ({}) {:?}",
image.name_with_tag(),
image,
),
));
}
_ => {}
};
match cmd::utilities::exec("docker", vec!["push", dest.as_str()]) {
Err(err) => match err {
CmdError::Exec(_exit_status) => return Err(PushError::ImagePushFailed),
CmdError::Io(err) => return Err(PushError::IoError(err)),
CmdError::Unexpected(err) => return Err(PushError::Unknown(err)),
},
Err(_) => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"failed to push image {:?} into DOCR {}",
image,
self.name_with_id(),
),
));
}
_ => {}
};
@@ -82,12 +92,12 @@ impl DOCR {
Ok(PushResult { image })
}
fn get_or_create_repository(&self, _image: &Image) -> Result<(), ContainerRegistryError> {
fn get_or_create_repository(&self, _image: &Image) -> Result<(), EngineError> {
// TODO check if repository really exist
self.create_repository(&_image)
}
fn delete_repository(&self, _image: &Image) -> Result<(), ContainerRegistryError> {
fn delete_repository(&self, _image: &Image) -> Result<(), EngineError> {
match cmd::utilities::exec(
"doctl",
vec![
@@ -99,11 +109,16 @@ impl DOCR {
self.api_key.as_str(),
],
) {
Err(err) => match err {
CmdError::Exec(exit_status) => return Err(ContainerRegistryError::Unknown),
CmdError::Io(err) => return Err(ContainerRegistryError::Unknown),
CmdError::Unexpected(err) => return Err(ContainerRegistryError::Unknown),
},
Err(_) => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"failed to delete DOCR repository {} from {}",
self.registry_name.as_str(),
self.name_with_id(),
),
));
}
_ => {}
};
Ok(())
@@ -127,7 +142,7 @@ impl ContainerRegistry for DOCR {
unimplemented!()
}
fn is_valid(&self) -> Result<(), ContainerRegistryError> {
fn is_valid(&self) -> Result<(), EngineError> {
unimplemented!()
}
@@ -135,19 +150,19 @@ impl ContainerRegistry for DOCR {
unimplemented!()
}
fn on_create(&self) -> Result<(), ContainerRegistryError> {
fn on_create(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_create_error(&self) -> Result<(), ContainerRegistryError> {
fn on_create_error(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_delete(&self) -> Result<(), ContainerRegistryError> {
fn on_delete(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_delete_error(&self) -> Result<(), ContainerRegistryError> {
fn on_delete_error(&self) -> Result<(), EngineError> {
unimplemented!()
}
@@ -156,10 +171,12 @@ impl ContainerRegistry for DOCR {
}
// https://www.digitalocean.com/docs/images/container-registry/how-to/use-registry-docker-kubernetes/
fn push(&self, image: &Image, _force_push: bool) -> Result<PushResult, PushError> {
fn push(&self, image: &Image, _force_push: bool) -> Result<PushResult, EngineError> {
let image = image.clone();
//TODO instead use get_or_create_repository
self.create_repository(&image);
// TODO 1/ instead use get_or_create_repository
// TODO 2/ does an error is returned if the repository already exist or not?
self.create_repository(&image)?;
match cmd::utilities::exec(
"doctl",
vec![
@@ -170,19 +187,23 @@ impl ContainerRegistry for DOCR {
self.api_key.as_str(),
],
) {
Err(err) => match err {
CmdError::Exec(_exit_status) => return Err(PushError::CredentialsError),
CmdError::Io(err) => return Err(PushError::IoError(err)),
CmdError::Unexpected(err) => return Err(PushError::Unknown(err)),
},
Err(_) => {
return Err(
self.engine_error(
EngineErrorCause::User("Your DOCR account seems to be no longer valid (bad Credentials). \
Please contact your Organization administrator to fix or change the Credentials."),
format!("failed to login to DOCR {}", self.name_with_id()))
);
}
_ => {}
};
//TODO check force or not
let dest = format!("{}:{}", self.registry_name.as_str(), image.tag.as_str());
self.push_image(dest, &image)
}
fn push_error(&self, _image: &Image) -> Result<PushResult, PushError> {
fn push_error(&self, _image: &Image) -> Result<PushResult, EngineError> {
unimplemented!()
}
}

View File

@@ -12,10 +12,8 @@ use rusoto_sts::{GetCallerIdentityRequest, Sts, StsClient};
use crate::build_platform::Image;
use crate::cmd;
use crate::cmd::utilities::CmdError;
use crate::container_registry::{
ContainerRegistry, ContainerRegistryError, Kind, PushError, PushResult,
};
use crate::container_registry::{ContainerRegistry, Kind, PushResult};
use crate::error::{EngineError, EngineErrorCause};
use crate::models::{
Context, Listener, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressListener,
ProgressScope,
@@ -112,7 +110,7 @@ impl ECR {
}
}
fn push_image(&self, dest: String, image: &Image) -> Result<PushResult, PushError> {
fn push_image(&self, dest: String, image: &Image) -> Result<PushResult, EngineError> {
// READ https://docs.aws.amazon.com/AmazonECR/latest/userguide/docker-push-ecr-image.html
// docker tag e9ae3c220b23 aws_account_id.dkr.ecr.region.amazonaws.com/my-web-app
@@ -121,11 +119,16 @@ impl ECR {
vec!["tag", image.name_with_tag().as_str(), dest.as_str()],
self.docker_envs(),
) {
Err(err) => match err {
CmdError::Exec(_exit_status) => return Err(PushError::ImageTagFailed),
CmdError::Io(err) => return Err(PushError::IoError(err)),
CmdError::Unexpected(err) => return Err(PushError::Unknown(err)),
},
Err(_) => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"failed to tag image ({}) {:?}",
image.name_with_tag(),
image,
),
));
}
_ => {}
};
@@ -135,11 +138,16 @@ impl ECR {
vec!["push", dest.as_str()],
self.docker_envs(),
) {
Err(err) => match err {
CmdError::Exec(_exit_status) => return Err(PushError::ImagePushFailed),
CmdError::Io(err) => return Err(PushError::IoError(err)),
CmdError::Unexpected(err) => return Err(PushError::Unknown(err)),
},
Err(_) => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"failed to push image {:?} into ECR {}",
image,
self.name_with_id(),
),
));
}
_ => {}
};
@@ -149,7 +157,7 @@ impl ECR {
Ok(PushResult { image })
}
fn create_repository(&self, image: &Image) -> Result<Repository, ContainerRegistryError> {
fn create_repository(&self, image: &Image) -> Result<Repository, EngineError> {
info!("ECR create repository {}", image.name.as_str());
let mut crr = CreateRepositoryRequest::default();
crr.repository_name = image.name.clone();
@@ -158,7 +166,16 @@ impl ECR {
match r {
Err(err) => match err {
RusotoError::Service(ref err) => info!("{:?}", err),
_ => return Err(ContainerRegistryError::from(err)),
_ => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"can't create ECR repository {} for {}",
image.name.as_str(),
self.name_with_id()
),
));
}
},
_ => {}
}
@@ -191,15 +208,19 @@ impl ECR {
let r = async_run(self.ecr_client().put_lifecycle_policy(plp));
match r {
Err(err) => Err(ContainerRegistryError::from(err)),
Err(err) => Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"can't set lifecycle policy to ECR repository {} for {}",
image.name.as_str(),
self.name_with_id()
),
)),
_ => Ok(self.get_repository(&image).unwrap()),
}
}
fn get_or_create_repository(
&self,
image: &Image,
) -> Result<Repository, ContainerRegistryError> {
fn get_or_create_repository(&self, image: &Image) -> Result<Repository, EngineError> {
// check if the repository already exists
let repository = self.get_repository(&image);
if repository.is_some() {
@@ -228,13 +249,19 @@ impl ContainerRegistry for ECR {
self.name.as_str()
}
fn is_valid(&self) -> Result<(), ContainerRegistryError> {
fn is_valid(&self) -> Result<(), EngineError> {
let client = StsClient::new_with_client(self.client(), Region::default());
let s = async_run(client.get_caller_identity(GetCallerIdentityRequest::default()));
match s {
Ok(_x) => Ok(()),
Err(err) => Err(ContainerRegistryError::from(err)),
Ok(_) => Ok(()),
Err(_) => Err(self.engine_error(
EngineErrorCause::User(
"Your ECR account seems to be no longer valid (bad Credentials). \
Please contact your Organization administrator to fix or change the Credentials.",
),
format!("bad ECR credentials for {}", self.name_with_id()),
)),
}
}
@@ -242,28 +269,28 @@ impl ContainerRegistry for ECR {
self.listeners.push(listener);
}
fn on_create(&self) -> Result<(), ContainerRegistryError> {
fn on_create(&self) -> Result<(), EngineError> {
info!("ECR.on_create() called");
Ok(())
}
fn on_create_error(&self) -> Result<(), ContainerRegistryError> {
fn on_create_error(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_delete(&self) -> Result<(), ContainerRegistryError> {
fn on_delete(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn on_delete_error(&self) -> Result<(), ContainerRegistryError> {
fn on_delete_error(&self) -> Result<(), EngineError> {
unimplemented!()
}
fn does_image_exists(&self, image: &Image) -> bool {
self.get_repository(&image).is_some()
self.get_repository(image).is_some()
}
fn push(&self, image: &Image, force_push: bool) -> Result<PushResult, PushError> {
fn push(&self, image: &Image, force_push: bool) -> Result<PushResult, EngineError> {
let r = async_run(
self.ecr_client()
.get_authorization_token(GetAuthorizationTokenRequest::default()),
@@ -286,18 +313,43 @@ impl ContainerRegistry for ECR {
ad.clone().proxy_endpoint.unwrap(),
)
}
None => return Err(PushError::RepositoryInitFailure),
None => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"failed to retrieve credentials and endpoint URL from ECR {}",
self.name_with_id(),
),
));
}
},
_ => return Err(PushError::RepositoryInitFailure),
_ => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"failed to retrieve credentials and endpoint URL from ECR {}",
self.name_with_id(),
),
));
}
};
let repository = match if force_push {
self.create_repository(&image)
self.create_repository(image)
} else {
self.get_or_create_repository(&image)
self.get_or_create_repository(image)
} {
Ok(r) => r,
_ => return Err(PushError::RepositoryInitFailure),
_ => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"failed to create ECR repository for {} with image {:?}",
self.name_with_id(),
image,
),
));
}
};
match cmd::utilities::exec_with_envs(
@@ -312,11 +364,12 @@ impl ContainerRegistry for ECR {
],
self.docker_envs(),
) {
Err(err) => match err {
CmdError::Exec(_exit_status) => return Err(PushError::CredentialsError),
CmdError::Io(err) => return Err(PushError::IoError(err)),
CmdError::Unexpected(err) => return Err(PushError::Unknown(err)),
},
Err(_) => return Err(
self.engine_error(
EngineErrorCause::User("Your ECR account seems to be no longer valid (bad Credentials). \
Please contact your Organization administrator to fix or change the Credentials."),
format!("failed to login to ECR {}", self.name_with_id()))
),
_ => {}
};
@@ -373,7 +426,7 @@ impl ContainerRegistry for ECR {
self.push_image(dest, image)
}
fn push_error(&self, image: &Image) -> Result<PushResult, PushError> {
fn push_error(&self, image: &Image) -> Result<PushResult, EngineError> {
// TODO change this
Ok(PushResult {
image: image.clone(),

View File

@@ -5,6 +5,7 @@ use rusoto_core::RusotoError;
use serde::{Deserialize, Serialize};
use crate::build_platform::Image;
use crate::error::{EngineError, EngineErrorCause, EngineErrorScope};
use crate::models::{Context, Listener, ProgressListener};
pub mod docker_hub;
@@ -16,61 +17,38 @@ pub trait ContainerRegistry {
fn kind(&self) -> Kind;
fn id(&self) -> &str;
fn name(&self) -> &str;
fn is_valid(&self) -> Result<(), ContainerRegistryError>;
fn name_with_id(&self) -> String {
format!("{} ({})", self.name(), self.id())
}
fn is_valid(&self) -> Result<(), EngineError>;
fn add_listener(&mut self, listener: Listener);
fn on_create(&self) -> Result<(), ContainerRegistryError>;
fn on_create_error(&self) -> Result<(), ContainerRegistryError>;
fn on_delete(&self) -> Result<(), ContainerRegistryError>;
fn on_delete_error(&self) -> Result<(), ContainerRegistryError>;
fn on_create(&self) -> Result<(), EngineError>;
fn on_create_error(&self) -> Result<(), EngineError>;
fn on_delete(&self) -> Result<(), EngineError>;
fn on_delete_error(&self) -> Result<(), EngineError>;
fn does_image_exists(&self, image: &Image) -> bool;
fn push(&self, image: &Image, force_push: bool) -> Result<PushResult, PushError>;
fn push_error(&self, image: &Image) -> Result<PushResult, PushError>;
fn push(&self, image: &Image, force_push: bool) -> Result<PushResult, EngineError>;
fn push_error(&self, image: &Image) -> Result<PushResult, EngineError>;
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::ContainerRegistry(self.id().to_string(), self.name().to_string())
}
fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError {
EngineError::new(
cause,
self.engine_error_scope(),
self.context().execution_id(),
Some(message),
)
}
}
pub struct PushResult {
pub image: Image,
}
#[derive(Debug)]
pub enum PushError {
RepositoryInitFailure,
CredentialsError,
IoError(std::io::Error),
ImageTagFailed,
ImagePushFailed,
ImageAlreadyExists,
Unknown(String),
}
#[derive(Serialize, Deserialize, Clone)]
pub enum Kind {
DockerHub,
ECR,
DOCR,
}
#[derive(Debug, Eq, PartialEq)]
pub enum ContainerRegistryError {
Credentials,
Unknown,
}
impl<E> From<RusotoError<E>> for ContainerRegistryError {
fn from(error: RusotoError<E>) -> Self {
match error {
RusotoError::Credentials(_) => ContainerRegistryError::Credentials,
RusotoError::Service(_) => ContainerRegistryError::Unknown,
RusotoError::HttpDispatch(_) => ContainerRegistryError::Unknown,
RusotoError::Validation(_) => ContainerRegistryError::Unknown,
RusotoError::ParseError(_) => ContainerRegistryError::Unknown,
RusotoError::Unknown(e) => {
if e.status == 403 {
ContainerRegistryError::Credentials
} else {
ContainerRegistryError::Unknown
}
}
RusotoError::Blocking => ContainerRegistryError::Unknown,
}
}
}

View File

@@ -1,6 +1,7 @@
use std::net::Ipv4Addr;
use crate::dns_provider::{DnsProvider, DnsProviderError, Kind};
use crate::dns_provider::{DnsProvider, Kind};
use crate::error::{EngineError, EngineErrorCause};
use crate::models::Context;
pub struct Cloudflare {
@@ -65,9 +66,15 @@ impl DnsProvider for Cloudflare {
vec![Ipv4Addr::new(1, 1, 1, 1), Ipv4Addr::new(1, 0, 0, 1)]
}
fn is_valid(&self) -> Result<(), DnsProviderError> {
fn is_valid(&self) -> Result<(), EngineError> {
if self.cloudflare_api_token.is_empty() || self.cloudflare_email.is_empty() {
Err(DnsProviderError::Credentials)
Err(self.engine_error(
EngineErrorCause::User(
"Your Cloudflare account seems to be no longer valid (bad Credentials). \
Please contact your Organization administrator to fix or change the Credentials.",
),
format!("bad Cloudflare credentials for {}", self.name_with_id()),
))
} else {
Ok(())
}

View File

@@ -2,6 +2,7 @@ use std::net::Ipv4Addr;
use serde::{Deserialize, Serialize};
use crate::error::{EngineError, EngineErrorCause, EngineErrorScope};
use crate::models::Context;
pub mod cloudflare;
@@ -16,15 +17,23 @@ pub trait DnsProvider {
fn kind(&self) -> Kind;
fn id(&self) -> &str;
fn name(&self) -> &str;
fn name_with_id(&self) -> String {
format!("{} ({})", self.name(), self.id())
}
fn account(&self) -> &str;
fn token(&self) -> &str;
fn domain(&self) -> &str;
fn resolvers(&self) -> Vec<Ipv4Addr>;
fn is_valid(&self) -> Result<(), DnsProviderError>;
}
#[derive(Debug, Eq, PartialEq)]
pub enum DnsProviderError {
Credentials,
Unknown,
fn is_valid(&self) -> Result<(), EngineError>;
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::DnsProvider(self.id().to_string(), self.name().to_string())
}
fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError {
EngineError::new(
cause,
self.engine_error_scope(),
self.context().execution_id(),
Some(message),
)
}
}

View File

@@ -3,8 +3,8 @@ use std::borrow::Borrow;
use crate::build_platform::BuildPlatform;
use crate::cloud_provider::CloudProvider;
use crate::container_registry::ContainerRegistry;
use crate::dns_provider::{DnsProvider, DnsProviderError};
use crate::error::ConfigurationError;
use crate::dns_provider::DnsProvider;
use crate::error::EngineError;
use crate::models::Context;
use crate::session::Session;
@@ -50,44 +50,22 @@ impl<'a> Engine {
pub fn cloud_provider(&self) -> &dyn CloudProvider {
self.cloud_provider.borrow()
}
pub fn dns_provider(&self) -> &dyn DnsProvider {
self.dns_provider.borrow()
}
pub fn is_valid(&self) -> Result<(), ConfigurationError> {
match self.build_platform.is_valid() {
Ok(_) => {}
Err(err) => {
return Err(ConfigurationError::BuildPlatform(err));
}
}
match self.container_registry.is_valid() {
Ok(_) => {}
Err(err) => {
return Err(ConfigurationError::ContainerRegistry(err));
}
}
match self.cloud_provider.is_valid() {
Ok(_) => {}
Err(err) => {
return Err(ConfigurationError::CloudProvider(err));
}
}
match self.dns_provider.is_valid() {
Ok(_) => {}
Err(err) => {
return Err(ConfigurationError::DnsProvider(err));
}
}
pub fn is_valid(&self) -> Result<(), EngineError> {
self.build_platform.is_valid()?;
self.container_registry.is_valid()?;
self.cloud_provider.is_valid()?;
self.dns_provider.is_valid()?;
Ok(())
}
/// check and init the connection to all the services
pub fn session(&'a self) -> Result<Session<'a>, ConfigurationError> {
pub fn session(&'a self) -> Result<Session<'a>, EngineError> {
match self.is_valid() {
Ok(_) => Ok(Session::<'a> { engine: self }),
Err(err) => Err(err),

View File

@@ -1,12 +1,117 @@
use crate::build_platform::error::BuildPlatformError;
use crate::cloud_provider::CloudProviderError;
use crate::container_registry::ContainerRegistryError;
use crate::dns_provider::DnsProviderError;
use std::error::Error;
use std::process::ExitStatus;
use rusoto_core::RusotoError;
pub type Type = String;
pub type Id = String;
pub type Name = String;
#[derive(Debug)]
pub enum ConfigurationError {
BuildPlatform(BuildPlatformError),
ContainerRegistry(ContainerRegistryError),
CloudProvider(CloudProviderError),
DnsProvider(DnsProviderError),
pub struct EngineError {
pub cause: EngineErrorCause,
pub scope: EngineErrorScope,
pub execution_id: String,
pub message: Option<String>,
}
impl EngineError {
pub fn new<T, S>(
cause: EngineErrorCause,
scope: EngineErrorScope,
execution_id: T,
message: Option<S>,
) -> Self
where
T: Into<String>,
S: Into<String>,
{
EngineError {
cause,
scope,
execution_id: execution_id.into(),
message: match message {
Some(message) => Some(message.into()),
_ => None,
},
}
}
}
#[derive(Debug)]
pub enum EngineErrorScope {
Engine,
BuildPlatform(Id, Name),
ContainerRegistry(Id, Name),
CloudProvider(Id, Name),
Kubernetes(Id, Name),
DnsProvider(Id, Name),
Environment(Id, Name),
Database(Id, Type, Name),
Application(Id, Name),
Router(Id, Name),
ExternalService(Id, Name),
}
#[derive(Debug)]
pub enum EngineErrorCause {
Internal,
User(&'static str),
}
#[derive(Debug)]
pub struct SimpleError {
pub kind: SimpleErrorKind,
pub message: Option<String>,
}
#[derive(Debug)]
pub enum SimpleErrorKind {
Command(ExitStatus),
Other,
}
impl SimpleError {
pub fn new<T: Into<String>>(kind: SimpleErrorKind, message: Option<T>) -> Self {
SimpleError {
kind,
message: match message {
Some(message) => Some(message.into()),
_ => None,
},
}
}
}
impl From<std::io::Error> for SimpleError {
fn from(err: std::io::Error) -> Self {
SimpleError::new(SimpleErrorKind::Other, Some(err.to_string()))
}
}
pub fn from_simple_error_to_engine_error<X, T: Into<String>>(
scope: EngineErrorScope,
execution_id: T,
input: Result<X, SimpleError>,
) -> Result<X, EngineError> {
match input {
Err(simple_error) => {
let message = match simple_error.kind {
SimpleErrorKind::Command(exit_status) => format!(
"{} (exit status: {})",
simple_error.message.unwrap_or("<no message>".into()),
exit_status
),
SimpleErrorKind::Other => simple_error.message.unwrap_or("<no message>".into()),
};
Err(EngineError::new(
EngineErrorCause::Internal,
scope,
execution_id,
Some(message),
))
}
Ok(x) => Ok(x),
}
}

View File

@@ -1,3 +1,11 @@
use std::fmt::Display;
use std::fs::{read_to_string, File};
use std::io::{Error, ErrorKind, Read, Write};
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::str::FromStr;
use std::{fs, io};
use retry::delay::Fibonacci;
use retry::OperationResult;
use rusoto_core::{Client, HttpClient, Region, RusotoError};
@@ -7,23 +15,20 @@ use rusoto_s3::{
GetObjectRequest, ListObjectsV2Output, ListObjectsV2Request, PutBucketVersioningRequest,
S3Client, VersioningConfiguration, S3,
};
use std::fmt::Display;
use std::fs::{read_to_string, File};
use std::io::{Error, ErrorKind, Read, Write};
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::{fs, io};
use crate::cmd::utilities::exec_with_envs;
use crate::error::{SimpleError, SimpleErrorKind};
use crate::runtime::async_run;
pub const AWS_REGION_FOR_S3_US: &str = "ap-south-1";
use crate::cmd::utilities::{exec_with_envs, CmdError};
use crate::runtime::async_run;
use std::str::FromStr;
pub type FileContent = String;
pub fn create_bucket(
access_key_id: &str,
secret_access_key: &str,
bucket_name: &str,
) -> Result<(), CmdError> {
) -> Result<(), SimpleError> {
exec_with_envs(
"aws",
vec!["s3api", "create-bucket", "--bucket", &bucket_name],
@@ -34,15 +39,13 @@ pub fn create_bucket(
)
}
pub type FileContent = String;
pub fn get_object(
access_key_id: &str,
secret_access_key: &str,
region: &Region,
bucket_name: &str,
object_key: &str,
) -> Result<FileContent, Error> {
) -> Result<FileContent, SimpleError> {
let credentials = StaticProvider::new(
access_key_id.to_string(),
secret_access_key.to_string(),
@@ -59,12 +62,12 @@ pub fn get_object(
let get_object_output = s3_client.get_object(or);
let r = async_run(get_object_output);
let _err = Error::new(
ErrorKind::Other,
format!(
let _err = SimpleError::new(
SimpleErrorKind::Other,
Some(format!(
"something goes wrong while getting object {} in the S3 bucket {}",
object_key, bucket_name
),
)),
);
match r {
@@ -90,9 +93,9 @@ pub fn get_object(
RusotoError::Service(s) => match s {
GetObjectError::NoSuchKey(x) => {
info!("no such key '{}': {}", object_key, x.as_str());
Err(Error::new(
ErrorKind::NotFound,
format!("no such key '{}': {}", object_key, x.as_str()),
Err(SimpleError::new(
SimpleErrorKind::Other,
Some(format!("no such key '{}': {}", object_key, x.as_str())),
))
}
},
@@ -107,7 +110,10 @@ pub fn get_object(
match r_from_aws_cli {
Ok(..) => Ok(r_from_aws_cli.unwrap()),
Err(err) => {
error!("{}", err);
if let Some(message) = err.message {
error!("{}", message);
}
Err(_err)
}
}
@@ -125,21 +131,19 @@ fn get_object_via_aws_cli(
secret_access_key: &str,
bucket_name: &str,
object_key: &str,
) -> Result<FileContent, Error> {
) -> Result<FileContent, SimpleError> {
let s3_url = format!("s3://{}/{}", bucket_name, object_key);
let local_path = format!("/tmp/{}", object_key);
let r = exec_with_envs(
exec_with_envs(
"aws",
vec!["s3", "cp", &s3_url, &local_path],
vec![
("AWS_ACCESS_KEY_ID", &access_key_id),
("AWS_SECRET_ACCESS_KEY", &secret_access_key),
],
);
match r {
Err(e) => return Err(Error::new(ErrorKind::Other, e)),
_ => {}
};
)?;
let s = read_to_string(&local_path)?;
Ok(s)
}
@@ -151,7 +155,7 @@ pub fn get_kubernetes_config_file<P>(
kubernetes_config_bucket_name: &str,
kubernetes_config_object_key: &str,
file_path: P,
) -> Result<File, Error>
) -> Result<File, SimpleError>
where
P: AsRef<Path>,
{
@@ -183,9 +187,9 @@ where
let file_content = match file_content_result {
Ok(file_content) => file_content,
Err(_) => {
return Err(Error::new(
ErrorKind::InvalidData,
"file content is empty (retry failed multiple times) - which is not the expected content - what's wrong?",
return Err(SimpleError::new(
SimpleErrorKind::Other,
Some("file content is empty (retry failed multiple times) - which is not the expected content - what's wrong?"),
));
}
};
@@ -204,7 +208,7 @@ pub fn list_objects_in(
access_key_id: &str,
secret_access_key: &str,
bucket_name: &str,
) -> Result<ListObjectsV2Output, Error> {
) -> Result<ListObjectsV2Output, SimpleError> {
let credentials = StaticProvider::new(
access_key_id.to_string(),
secret_access_key.to_string(),
@@ -213,13 +217,19 @@ pub fn list_objects_in(
);
let client = Client::new_with(credentials, HttpClient::new().unwrap());
let s3_client = S3Client::new_with_client(client, get_default_region_for_us());
let mut list_request = ListObjectsV2Request::default();
list_request.bucket = bucket_name.to_string();
let lis_object = s3_client.list_objects_v2(list_request);
let objects_in = async_run(lis_object);
match objects_in {
Ok(objects) => Ok(objects),
Err(err) => Err(Error::new(ErrorKind::Other, err)),
Err(err) => Err(SimpleError::new(
SimpleErrorKind::Other,
Some(format!("error listing objects from s3 {:?}", err)),
)),
}
}
@@ -228,7 +238,7 @@ pub fn delete_bucket(
access_key_id: &str,
secret_access_key: &str,
bucket_name: &str,
) -> Result<(), CmdError> {
) -> Result<(), SimpleError> {
info!("Deleting S3 Bucket {}", bucket_name.clone());
match exec_with_envs(
"aws",
@@ -249,7 +259,10 @@ pub fn delete_bucket(
return Ok(o);
}
Err(e) => {
error!("while deleting bucket {}", e);
error!(
"while deleting bucket {}",
e.message.as_ref().unwrap_or(&"".into())
);
return Err(e);
}
}
@@ -265,7 +278,7 @@ pub fn push_object(
bucket_name: &str,
object_key: &str,
local_file_path: &str,
) -> Result<(), CmdError> {
) -> Result<(), SimpleError> {
info!(
"Pushing object {} to bucket {}",
local_file_path.clone(),
@@ -289,7 +302,10 @@ pub fn push_object(
return Ok(o);
}
Err(e) => {
error!("While uploading object {}", e);
error!(
"While uploading object {}",
e.message.as_ref().unwrap_or(&"".into())
);
return Err(e);
}
}

View File

@@ -5,6 +5,7 @@ use std::io::{Error, ErrorKind, Write};
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use crate::error::{SimpleError, SimpleErrorKind};
use tera::Error as TeraError;
use tera::{Context, Tera};
use walkdir::WalkDir;
@@ -13,7 +14,7 @@ pub fn generate_and_copy_all_files_into_dir<S, P>(
from_dir: S,
to_dir: P,
context: &Context,
) -> Result<(), Error>
) -> Result<(), SimpleError>
where
S: AsRef<Path> + Copy,
P: AsRef<Path> + Copy,
@@ -49,7 +50,7 @@ where
};
error!("{}", error_msg.as_str());
return Err(Error::new(ErrorKind::InvalidData, error_msg));
return Err(SimpleError::new(SimpleErrorKind::Other, Some(error_msg)));
}
};
@@ -61,12 +62,15 @@ where
Ok(())
}
pub fn copy_non_template_files<S, P>(from: S, to: P) -> Result<(), Error>
pub fn copy_non_template_files<S, P>(from: S, to: P) -> Result<(), SimpleError>
where
S: AsRef<Path>,
P: AsRef<Path>,
{
crate::fs::copy_files(from.as_ref(), to.as_ref(), true)
match crate::fs::copy_files(from.as_ref(), to.as_ref(), true) {
Err(err) => Err(SimpleError::from(err)),
Ok(x) => Ok(x),
}
}
pub fn generate_j2_template_files<P>(
@@ -117,7 +121,7 @@ where
pub fn write_rendered_templates(
rendered_templates: &[RenderedTemplate],
into: &Path,
) -> Result<(), Error> {
) -> Result<(), SimpleError> {
for rt in rendered_templates {
let dest = format!("{}/{}", into.to_str().unwrap(), rt.path_and_file_name());

View File

@@ -3,13 +3,11 @@ use std::thread;
use serde::{Deserialize, Serialize};
use crate::build_platform::BuildError;
use crate::cloud_provider::kubernetes::{Kubernetes, KubernetesError};
use crate::cloud_provider::service::ServiceError;
use crate::cloud_provider::kubernetes::Kubernetes;
use crate::cloud_provider::service::{Application, Service};
use crate::cloud_provider::DeployError;
use crate::container_registry::{PushError, PushResult};
use crate::container_registry::PushResult;
use crate::engine::Engine;
use crate::error::EngineError;
use crate::models::{
Action, Environment, EnvironmentAction, EnvironmentError, ListenersHelper, ProgressInfo,
ProgressLevel, ProgressScope,
@@ -30,10 +28,7 @@ impl<'a> Transaction<'a> {
}
}
pub fn create_kubernetes(
&mut self,
kubernetes: &'a dyn Kubernetes,
) -> Result<(), KubernetesError> {
pub fn create_kubernetes(&mut self, kubernetes: &'a dyn Kubernetes) -> Result<(), EngineError> {
match kubernetes.is_valid() {
Ok(_) => {
self.steps.push(Step::CreateKubernetes(kubernetes));
@@ -43,10 +38,7 @@ impl<'a> Transaction<'a> {
}
}
pub fn delete_kubernetes(
&mut self,
kubernetes: &'a dyn Kubernetes,
) -> Result<(), KubernetesError> {
pub fn delete_kubernetes(&mut self, kubernetes: &'a dyn Kubernetes) -> Result<(), EngineError> {
match kubernetes.is_valid() {
Ok(_) => {
self.steps.push(Step::DeleteKubernetes(kubernetes));
@@ -143,7 +135,7 @@ impl<'a> Transaction<'a> {
&self,
environment: &Environment,
option: &DeploymentOption,
) -> Result<Vec<Box<dyn Application>>, BuildError> {
) -> Result<Vec<Box<dyn Application>>, EngineError> {
let external_services_to_build = environment
.external_services
.iter()
@@ -238,7 +230,7 @@ impl<'a> Transaction<'a> {
&self,
applications: Vec<Box<dyn Application>>,
option: &DeploymentOption,
) -> Result<Vec<(Box<dyn Application>, PushResult)>, PushError> {
) -> Result<Vec<(Box<dyn Application>, PushResult)>, EngineError> {
let application_and_push_results: Vec<_> = applications
.into_iter()
.map(|mut app| {
@@ -276,18 +268,13 @@ impl<'a> Transaction<'a> {
environment: &crate::cloud_provider::environment::Environment,
) -> TransactionResult {
match environment.is_valid() {
Err(service_error) => {
warn!("ROLLBACK STARTED! an error occurred {:?}", service_error);
Err(engine_error) => {
warn!("ROLLBACK STARTED! an error occurred {:?}", engine_error);
return match self.rollback() {
Ok(_) => {
TransactionResult::Rollback(CommitError::NotValidService(service_error))
}
Ok(_) => TransactionResult::Rollback(engine_error),
Err(err) => {
error!("ROLLBACK FAILED! fatal error: {:?}", err);
TransactionResult::UnrecoverableError(
CommitError::NotValidService(service_error),
err,
)
TransactionResult::UnrecoverableError(engine_error, err)
}
};
}
@@ -303,14 +290,14 @@ impl<'a> Transaction<'a> {
Step::CreateKubernetes(kubernetes) => {
// revert kubernetes creation
match kubernetes.on_create_error() {
Err(err) => return Err(RollbackError::CreateKubernetes(err)),
Err(err) => return Err(RollbackError::CommitError(err)),
_ => {}
};
}
Step::DeleteKubernetes(kubernetes) => {
// revert kubernetes deletion
match kubernetes.on_delete_error() {
Err(err) => return Err(RollbackError::DeleteKubernetes(err)),
Err(err) => return Err(RollbackError::CommitError(err)),
_ => {}
};
}
@@ -408,14 +395,7 @@ impl<'a> Transaction<'a> {
let _ = match action {
Ok(_) => {}
Err(err) => {
return Err(match failover_environment.action {
Action::Create => RollbackError::DeployEnvironment(err),
Action::Pause => RollbackError::PauseEnvironment(err),
Action::Delete => RollbackError::DeleteEnvironment(err),
Action::Nothing => RollbackError::Error, // it can't happens
});
}
Err(err) => return Err(RollbackError::CommitError(err)),
};
Ok(())
@@ -433,14 +413,7 @@ impl<'a> Transaction<'a> {
let _ = match action {
Ok(_) => {}
Err(err) => {
return Err(match te.action {
Action::Create => RollbackError::DeployEnvironment(err),
Action::Pause => RollbackError::PauseEnvironment(err),
Action::Delete => RollbackError::DeleteEnvironment(err),
Action::Nothing => RollbackError::Error, // it can't happens
});
}
Err(err) => return Err(RollbackError::CommitError(err)),
};
Err(RollbackError::NoFailoverEnvironment)
@@ -463,15 +436,10 @@ impl<'a> Transaction<'a> {
Err(err) => {
warn!("ROLLBACK STARTED! an error occurred {:?}", err);
match self.rollback() {
Ok(_) => {
TransactionResult::Rollback(CommitError::CreateKubernetes(err))
}
Ok(_) => TransactionResult::Rollback(err),
Err(e) => {
error!("ROLLBACK FAILED! fatal error: {:?}", e);
TransactionResult::UnrecoverableError(
CommitError::CreateKubernetes(err),
e,
)
TransactionResult::UnrecoverableError(err, e)
}
}
}
@@ -484,15 +452,10 @@ impl<'a> Transaction<'a> {
Err(err) => {
warn!("ROLLBACK STARTED! an error occurred {:?}", err);
match self.rollback() {
Ok(_) => {
TransactionResult::Rollback(CommitError::DeleteKubernetes(err))
}
Ok(_) => TransactionResult::Rollback(err),
Err(e) => {
error!("ROLLBACK FAILED! fatal error: {:?}", e);
TransactionResult::UnrecoverableError(
CommitError::DeleteKubernetes(err),
e,
)
TransactionResult::UnrecoverableError(err, e)
}
}
}
@@ -514,9 +477,9 @@ impl<'a> Transaction<'a> {
Ok(applications)
}
Err(err) => Err(CommitError::PushImage(err)),
Err(err) => Err(err),
},
Err(err) => Err(CommitError::BuildImage(err)),
Err(err) => Err(err),
};
if apps_result.is_err() {
@@ -542,7 +505,6 @@ impl<'a> Transaction<'a> {
*environment_action,
&applications_by_environment,
|qe_env| kubernetes.deploy_environment(qe_env),
|err| CommitError::DeployEnvironment(err),
) {
TransactionResult::Ok => {}
err => return err,
@@ -555,7 +517,6 @@ impl<'a> Transaction<'a> {
*environment_action,
&applications_by_environment,
|qe_env| kubernetes.pause_environment(qe_env),
|err| CommitError::PauseEnvironment(err),
) {
TransactionResult::Ok => {}
err => return err,
@@ -568,7 +529,6 @@ impl<'a> Transaction<'a> {
*environment_action,
&applications_by_environment,
|qe_env| kubernetes.delete_environment(qe_env),
|err| CommitError::DeleteEnvironment(err),
) {
TransactionResult::Ok => {}
err => return err,
@@ -580,17 +540,15 @@ impl<'a> Transaction<'a> {
TransactionResult::Ok
}
fn commit_environment<F, E>(
fn commit_environment<F>(
&self,
kubernetes: &dyn Kubernetes,
environment_action: &EnvironmentAction,
applications_by_environment: &HashMap<&Environment, Vec<Box<dyn Application>>>,
action_fn: F,
commit_error: E,
) -> TransactionResult
where
F: Fn(&crate::cloud_provider::environment::Environment) -> Result<(), KubernetesError>,
E: Fn(KubernetesError) -> CommitError,
F: Fn(&crate::cloud_provider::environment::Environment) -> Result<(), EngineError>,
{
let target_environment = match environment_action {
EnvironmentAction::Environment(te) => te,
@@ -668,10 +626,10 @@ impl<'a> Transaction<'a> {
let _ = match action_fn(&qe_environment) {
Err(err) => {
let rollback_result = match self.rollback() {
Ok(_) => TransactionResult::Rollback(commit_error(err)),
Ok(_) => TransactionResult::Rollback(err),
Err(rollback_err) => {
error!("ROLLBACK FAILED! fatal error: {:?}", rollback_err);
TransactionResult::UnrecoverableError(commit_error(err), rollback_err)
TransactionResult::UnrecoverableError(err, rollback_err)
}
};
@@ -756,36 +714,15 @@ impl<'a> Clone for Step<'a> {
}
}
#[derive(Debug)]
pub enum CommitError {
CreateKubernetes(KubernetesError),
DeleteKubernetes(KubernetesError),
DeployEnvironment(KubernetesError),
PauseEnvironment(KubernetesError),
DeleteEnvironment(KubernetesError),
NotValidService(ServiceError),
BuildImage(BuildError),
PushImage(PushError),
DeployImage(DeployError),
}
#[derive(Debug)]
pub enum RollbackError {
CreateKubernetes(KubernetesError),
DeleteKubernetes(KubernetesError),
DeployEnvironment(KubernetesError),
PauseEnvironment(KubernetesError),
DeleteEnvironment(KubernetesError),
NotValidService(ServiceError),
BuildImage(BuildError),
PushImage(PushError),
DeployImage(DeployError),
CommitError(EngineError),
NoFailoverEnvironment,
Error,
Nothing,
}
pub enum TransactionResult {
Ok,
Rollback(CommitError),
UnrecoverableError(CommitError, RollbackError),
Rollback(EngineError),
UnrecoverableError(EngineError, RollbackError),
}

View File

@@ -6,9 +6,8 @@ use qovery_engine::build_platform::GitRepository;
use qovery_engine::cloud_provider::aws::kubernetes::node::Node;
use qovery_engine::cloud_provider::aws::kubernetes::EKS;
use qovery_engine::cloud_provider::aws::AWS;
use qovery_engine::cloud_provider::kubernetes::{Kubernetes, KubernetesError};
use qovery_engine::cloud_provider::kubernetes::Kubernetes;
use qovery_engine::cloud_provider::CloudProvider;
use qovery_engine::cmd::utilities::CmdError;
use qovery_engine::dns_provider::cloudflare::Cloudflare;
use qovery_engine::git::Credentials;
use qovery_engine::models::{Clone2, GitCredentials};

View File

@@ -30,8 +30,8 @@ fn delete_s3_bucket() {
bucket_name,
);
match creation {
Ok(out) => println!("Yippee Ki Yay"),
Err(e) => println!("While creating the bucket {}", e),
Ok(_) => println!("Yippee Ki Yay"),
Err(e) => println!("While creating the bucket {}", e.message.unwrap()),
}
let delete = delete_bucket(
@@ -41,6 +41,6 @@ fn delete_s3_bucket() {
);
match delete {
Ok(out) => println!("Yippee Ki Yay"),
Err(e) => println!("While deleting the bucket {}", e),
Err(e) => println!("While deleting the bucket {}", e.message.unwrap()),
}
}