This commit is contained in:
Benjamin Chastanier
2022-02-18 23:00:38 +01:00
parent d2c331552c
commit c7ffe23ea4
44 changed files with 2354 additions and 1017 deletions

View File

@@ -7,9 +7,11 @@ use sysinfo::{Disk, DiskExt, SystemExt};
use crate::build_platform::{docker, Build, BuildPlatform, BuildResult, CacheResult, Credentials, Image, Kind};
use crate::cmd::utilities::QoveryCommand;
use crate::error::{EngineError, EngineErrorCause, EngineErrorScope, SimpleError, SimpleErrorKind};
use crate::errors::{CommandError, EngineError};
use crate::events::{EngineEvent, EventDetails, EventMessage, ToTransmitter, Transmitter};
use crate::fs::workspace_directory;
use crate::git;
use crate::logger::{LogLevel, Logger};
use crate::models::{
Context, Listen, Listener, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope,
};
@@ -25,20 +27,22 @@ const BUILDPACKS_BUILDERS: [&str; 1] = [
];
/// use Docker in local
pub struct LocalDocker {
pub struct LocalDocker<'a> {
context: Context,
id: String,
name: String,
listeners: Listeners,
logger: &'a dyn Logger,
}
impl LocalDocker {
pub fn new(context: Context, id: &str, name: &str) -> Self {
impl<'a> LocalDocker<'a> {
pub fn new(context: Context, id: &str, name: &str, logger: &'a dyn Logger) -> Self {
LocalDocker {
context,
id: id.to_string(),
name: name.to_string(),
listeners: vec![],
logger,
}
}
@@ -64,9 +68,14 @@ impl LocalDocker {
match fs::read(dockerfile_path) {
Ok(bytes) => Ok(bytes),
Err(err) => {
let error_msg = format!("Can't read Dockerfile '{}'", dockerfile_path);
error!("{}, error: {:?}", error_msg, err);
Err(self.engine_error(EngineErrorCause::Internal, error_msg))
let engine_error = EngineError::new_docker_cannot_read_dockerfile(
self.get_event_details(),
dockerfile_path.to_string(),
CommandError::new(err.to_string(), None),
);
self.logger
.log(LogLevel::Error, EngineEvent::Error(engine_error.clone(), None));
Err(engine_error)
}
}
}
@@ -101,9 +110,14 @@ impl LocalDocker {
let env_var_args = match docker::match_used_env_var_args(env_var_args, dockerfile_content) {
Ok(env_var_args) => env_var_args,
Err(err) => {
let error_msg = format!("Can't extract env vars from Dockerfile '{}'", dockerfile_complete_path);
error!("{}, error: {:?}", error_msg, err);
return Err(self.engine_error(EngineErrorCause::Internal, error_msg));
let engine_error = EngineError::new_docker_cannot_extract_env_vars_from_dockerfile(
self.get_event_details(),
dockerfile_complete_path.to_string(),
CommandError::new(err.to_string(), None),
);
self.logger
.log(LogLevel::Error, EngineEvent::Error(engine_error.clone(), None));
Err(engine_error)
}
};
@@ -129,7 +143,10 @@ impl LocalDocker {
let exit_status = cmd.exec_with_timeout(
Duration::minutes(BUILD_DURATION_TIMEOUT_MIN),
|line| {
info!("{}", line);
self.logger.log(
LogLevel::Info,
EngineEvent::Info(self.get_event_details(), EventMessage::new_from_safe(line.to_string())),
);
lh.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {
@@ -141,7 +158,10 @@ impl LocalDocker {
));
},
|line| {
error!("{}", line);
self.logger.log(
LogLevel::Warning,
EngineEvent::Warning(self.get_event_details(), EventMessage::new_from_safe(line.to_string())),
);
lh.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {
@@ -156,15 +176,10 @@ impl LocalDocker {
match exit_status {
Ok(_) => Ok(BuildResult { build }),
Err(err) => Err(self.engine_error(
EngineErrorCause::User(
"It looks like there is something wrong in your Dockerfile. Try building the application locally with `docker build --no-cache`.",
),
format!(
"error while building container image {}. Error: {:?}",
self.name_with_id(),
err
),
Err(err) => Err(EngineError::new_docker_cannot_build_container_image(
self.get_event_details(),
self.name_with_id(),
CommandError::new(format!("{:?}", err), None),
)),
}
}
@@ -181,8 +196,8 @@ impl LocalDocker {
let args = self.context.docker_build_options();
let mut exit_status: Result<(), SimpleError> =
Err(SimpleError::new(SimpleErrorKind::Other, Some("no builder names")));
let mut exit_status: Result<(), CommandError> =
Err(CommandError::new_from_safe_message("No builder names".to_string()));
for builder_name in BUILDPACKS_BUILDERS.iter() {
let mut buildpacks_args = if !use_build_cache {
@@ -245,12 +260,13 @@ impl LocalDocker {
self.context.execution_id(),
));
let err = EngineError::new(
EngineErrorCause::Internal,
EngineErrorScope::Engine,
self.context.execution_id().to_string(),
Some(msg),
let err = EngineError::new_buildpack_invalid_language_format(
self.get_event_details(),
buildpacks_language.to_string(),
);
self.logger.log(LogLevel::Error, EngineEvent::Error(err.clone(), None));
return Err(err);
}
}
@@ -272,7 +288,10 @@ impl LocalDocker {
.exec_with_timeout(
Duration::minutes(BUILD_DURATION_TIMEOUT_MIN),
|line| {
info!("{}", line);
self.logger.log(
LogLevel::Info,
EngineEvent::Info(self.get_event_details(), EventMessage::new_from_safe(line.to_string())),
);
lh.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {
@@ -284,7 +303,13 @@ impl LocalDocker {
));
},
|line| {
error!("{}", line);
self.logger.log(
LogLevel::Warning,
EngineEvent::Warning(
self.get_event_details(),
EventMessage::new_from_safe(line.to_string()),
),
);
lh.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {
@@ -296,7 +321,7 @@ impl LocalDocker {
));
},
)
.map_err(|err| SimpleError::new(SimpleErrorKind::Other, Some(format!("{:?}", err))));
.map_err(|err| CommandError::new(format!("{:?}", err), None));
if exit_status.is_ok() {
// quit now if the builder successfully build the app
@@ -307,19 +332,17 @@ impl LocalDocker {
match exit_status {
Ok(_) => Ok(BuildResult { build }),
Err(err) => {
warn!("{:?}", err);
let error = EngineError::new_buildpack_cannot_build_container_image(
self.get_event_details(),
self.name_with_id(),
BUILDPACKS_BUILDERS.iter().map(|b| b.to_string()).collect(),
CommandError::new(format!("{:?}", err), None),
);
Err(self.engine_error(
EngineErrorCause::User(
"None builders supports Your application can't be built without providing a Dockerfile",
),
format!(
"Qovery can't build your container image {} with one of the following builders: {}. \
Please do provide a valid Dockerfile to build your application or contact the support.",
self.name_with_id(),
BUILDPACKS_BUILDERS.join(", ")
),
))
self.logger
.log(LogLevel::Error, EngineEvent::Error(error.clone(), None));
Err(error)
}
}
}
@@ -330,7 +353,12 @@ impl LocalDocker {
self.context.execution_id(),
format!("build/{}", build.image.name.as_str()),
)
.map_err(|err| self.engine_error(EngineErrorCause::Internal, err.to_string()))
.map_err(|err| {
EngineError::new_cannot_get_workspace_directory(
self.get_event_details(),
CommandError::new(err.to_string(), None),
)
})
}
}
@@ -353,11 +381,17 @@ impl BuildPlatform for LocalDocker {
fn is_valid(&self) -> Result<(), EngineError> {
if !crate::cmd::utilities::does_binary_exist("docker") {
return Err(self.engine_error(EngineErrorCause::Internal, String::from("docker binary not found")));
return Err(EngineError::new_missing_required_binary(
self.get_event_details(),
"docker".to_string(),
));
}
if !crate::cmd::utilities::does_binary_exist("pack") {
return Err(self.engine_error(EngineErrorCause::Internal, String::from("pack binary not found")));
return Err(EngineError::new_missing_required_binary(
self.get_event_details(),
"pack".to_string(),
));
}
Ok(())
@@ -369,9 +403,9 @@ impl BuildPlatform for LocalDocker {
// Check if a local cache layers for the container image exists.
let repository_root_path = self.get_repository_build_root_path(&build)?;
let parent_build = build
.to_previous_build(repository_root_path)
.map_err(|err| self.engine_error(EngineErrorCause::Internal, err.to_string()))?;
let parent_build = build.to_previous_build(repository_root_path).map_err(|err| {
EngineError::new_builder_get_build_error(self.get_event_details(), build.image.commit_id.to_string(), err)
})?;
let parent_build = match parent_build {
Some(parent_build) => parent_build,
@@ -449,8 +483,17 @@ impl BuildPlatform for LocalDocker {
"Error while cloning repository {}. Error: {:?}",
&build.git_repository.url, clone_error
);
error!("{}", message);
return Err(self.engine_error(EngineErrorCause::Internal, message));
let error = EngineError::new_builder_clone_repository_error(
self.get_event_details(),
build.git_repository.url.to_string(),
CommandError::new(clone_error.to_string(), None),
);
self.logger
.log(LogLevel::Error, EngineEvent::Error(error.clone(), None));
return Err(error);
}
let mut disable_build_cache = false;
@@ -481,9 +524,20 @@ impl BuildPlatform for LocalDocker {
for disk in system.get_disks() {
if disk.get_mount_point() == docker_path {
match check_docker_space_usage_and_clean(disk, self.get_docker_host_envs()) {
Ok(msg) => info!("{:?}", msg),
Err(e) => error!("{:?}", e.message),
let event_details = self.get_event_details();
if let Err(e) = check_docker_space_usage_and_clean(
disk,
self.get_docker_host_envs(),
event_details.clone(),
self.logger(),
) {
self.logger.log(
LogLevel::Warning,
EngineEvent::Warning(
event_details.clone(),
EventMessage::new(e.message_raw(), e.message_safe()),
),
);
}
break;
};
@@ -508,7 +562,6 @@ impl BuildPlatform for LocalDocker {
// If the dockerfile does not exist, abort
if !Path::new(dockerfile_absolute_path.as_str()).exists() {
warn!("Dockerfile not found under {}", dockerfile_absolute_path);
listeners_helper.error(ProgressInfo::new(
ProgressScope::Application {
id: build.image.application_id.clone(),
@@ -521,14 +574,13 @@ impl BuildPlatform for LocalDocker {
self.context.execution_id(),
));
return Err(self.engine_error(
EngineErrorCause::User("Dockerfile not found at location"),
format!(
"Your Dockerfile is not present at the specified location {}/{}",
build.git_repository.root_path.as_str(),
build.git_repository.dockerfile_path.unwrap_or_default().as_str()
),
));
let error =
EngineError::new_docker_cannot_find_dockerfile(self.get_event_details(), dockerfile_absolute_path);
self.logger
.log(LogLevel::Error, EngineEvent::Error(error.clone(), None));
return Err(error);
}
self.build_image_with_docker(
@@ -561,7 +613,14 @@ impl BuildPlatform for LocalDocker {
}
fn build_error(&self, build: Build) -> Result<BuildResult, EngineError> {
warn!("LocalDocker.build_error() called for {}", self.name());
let event_details = self.get_event_details();
self.logger.log(
LogLevel::Warning,
EngineEvent::Warning(
event_details.clone(),
EventMessage::new_from_safe(format!("LocalDocker.build_error() called for {}", self.name())),
),
);
let listener_helper = ListenersHelper::new(&self.listeners);
@@ -578,7 +637,11 @@ impl BuildPlatform for LocalDocker {
));
// FIXME
Err(self.engine_error(EngineErrorCause::Internal, message))
Err(EngineError::new_not_implemented_error(event_details))
}
fn logger(&self) -> &dyn Logger {
self.logger
}
}
@@ -592,38 +655,54 @@ impl Listen for LocalDocker {
}
}
impl ToTransmitter for LocalDocker {
fn to_transmitter(&self) -> Transmitter {
Transmitter::BuildPlatform(self.id().to_string(), self.name().to_string())
}
}
fn check_docker_space_usage_and_clean(
docker_path_size_info: &Disk,
envs: Vec<(&str, &str)>,
) -> Result<String, SimpleError> {
event_details: EventDetails,
logger: &dyn Logger,
) -> Result<(), CommandError> {
let docker_max_disk_percentage_usage_before_purge = 60; // arbitrary percentage that should make the job anytime
let available_space = docker_path_size_info.get_available_space();
let docker_percentage_remaining = available_space * 100 / docker_path_size_info.get_total_space();
if docker_percentage_remaining < docker_max_disk_percentage_usage_before_purge || available_space == 0 {
warn!(
"Docker disk remaining ({}%) is lower than {}%, requesting cleaning (purge)",
docker_percentage_remaining, docker_max_disk_percentage_usage_before_purge
logger.log(
LogLevel::Warning,
EngineEvent::Warning(
event_details.clone(),
EventMessage::new_from_safe(format!(
"Docker disk remaining ({}%) is lower than {}%, requesting cleaning (purge)",
docker_percentage_remaining, docker_max_disk_percentage_usage_before_purge
)),
),
);
return match docker_prune_images(envs) {
Err(e) => {
error!("error while purging docker images: {:?}", e.message);
Err(e)
}
_ => Ok("docker images have been purged".to_string()),
};
return docker_prune_images(envs);
};
Ok(format!(
"no need to purge old docker images, only {}% ({}/{}) disk used",
100 - docker_percentage_remaining,
docker_path_size_info.get_available_space(),
docker_path_size_info.get_total_space(),
))
logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(format!(
"No need to purge old docker images, only {}% ({}/{}) disk used",
100 - docker_percentage_remaining,
docker_path_size_info.get_available_space(),
docker_path_size_info.get_total_space(),
)),
),
);
Ok(())
}
fn docker_prune_images(envs: Vec<(&str, &str)>) -> Result<(), SimpleError> {
fn docker_prune_images(envs: Vec<(&str, &str)>) -> Result<(), CommandError> {
let all_prunes_commands = vec![
vec!["container", "prune", "-f"],
vec!["image", "prune", "-a", "-f"],
@@ -631,20 +710,19 @@ fn docker_prune_images(envs: Vec<(&str, &str)>) -> Result<(), SimpleError> {
vec!["volume", "prune", "-f"],
];
let mut errored_commands = vec![];
for prune in all_prunes_commands {
let mut cmd = QoveryCommand::new("docker", &prune, &envs);
match cmd.exec_with_timeout(
Duration::minutes(BUILD_DURATION_TIMEOUT_MIN),
|line| {
debug!("{}", line);
},
|line| {
debug!("{}", line);
},
) {
Ok(_) => {}
Err(e) => error!("error while puring {}. {:?}", prune[0], e),
};
if let Err(e) = cmd.exec_with_timeout(Duration::minutes(BUILD_DURATION_TIMEOUT_MIN), |_| {}, |_| {}) {
errored_commands.push(format!("{} {:?}", prune[0], e));
}
}
if errored_commands.len() > 0 {
return Err(CommandError::new(
errored_commands.join("/ "),
Some("Error while trying to prune images.".to_string()),
));
}
Ok(())

View File

@@ -1,9 +1,11 @@
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use crate::error::{EngineError, EngineErrorCause, EngineErrorScope};
use crate::errors::{CommandError, EngineError};
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter};
use crate::git;
use crate::models::{Context, Listen};
use crate::logger::Logger;
use crate::models::{Context, Listen, QoveryIdentifier};
use crate::utilities::get_image_tag;
use git2::{Cred, CredentialType, Error};
use std::fmt::{Display, Formatter, Result as FmtResult};
@@ -12,7 +14,7 @@ use std::path::Path;
pub mod docker;
pub mod local_docker;
pub trait BuildPlatform: Listen {
pub trait BuildPlatform: ToTransmitter {
fn context(&self) -> &Context;
fn kind(&self) -> Kind;
fn id(&self) -> &str;
@@ -24,15 +26,17 @@ pub trait BuildPlatform: Listen {
fn has_cache(&self, build: &Build) -> Result<CacheResult, EngineError>;
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),
fn logger(&self) -> &dyn Logger;
fn get_event_details(&self) -> EventDetails {
let context = self.context();
EventDetails::new(
None,
QoveryIdentifier::from(context.organization_id().to_string()),
QoveryIdentifier::from(context.cluster_id().to_string()),
QoveryIdentifier::from(context.execution_id().to_string()),
None,
Stage::Environment(EnvironmentStep::Build),
self.to_transmitter(),
)
}
}
@@ -44,7 +48,7 @@ pub struct Build {
}
impl Build {
pub fn to_previous_build<P>(&self, clone_repo_into_dir: P) -> Result<Option<Build>, Error>
pub fn to_previous_build<P>(&self, clone_repo_into_dir: P) -> Result<Option<Build>, CommandError>
where
P: AsRef<Path>,
{
@@ -59,7 +63,8 @@ impl Build {
Cred::userpass_plaintext(creds.login.as_str(), creds.password.as_str()).unwrap(),
)],
},
)?;
)
.map_err(|err| CommandError::new(err.to_string(), Some("Cannot get parent commit ID.".to_string())))?;
let parent_commit_id = match parent_commit_id {
None => return Ok(None),

View File

@@ -91,7 +91,7 @@ impl<'a> Application<'a> {
}
}
impl<'a> crate::cloud_provider::service::Application<'a> for Application<'a> {
impl<'a> crate::cloud_provider::service::Application for Application<'a> {
fn image(&self) -> &Image {
&self.image
}
@@ -123,7 +123,7 @@ impl<'a> Helm for Application<'a> {
}
}
impl<'a> StatelessService<'a> for Application<'a> {}
impl<'a> StatelessService for Application<'a> {}
impl<'a> ToTransmitter for Application<'a> {
fn to_transmitter(&self) -> Transmitter {
@@ -303,7 +303,7 @@ impl<'a> Service for Application<'a> {
}
fn logger(&self) -> &dyn Logger {
todo!()
self.logger
}
fn selector(&self) -> Option<String> {

View File

@@ -185,7 +185,9 @@ impl<'a> Service for MongoDB<'a> {
context.insert("namespace", environment.namespace());
let version = self.matching_correct_version(self.is_managed_service(), event_details.clone())?;
let version = self
.matching_correct_version(self.is_managed_service(), event_details.clone())?
.matched_version();
context.insert("version", &version);
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
@@ -367,12 +369,12 @@ impl<'a> Delete for MongoDB<'a> {
self.struct_name(),
function_name!(),
self.name(),
event_details,
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || {
delete_stateful_service(target, self, event_details.clone(), self.logger)
delete_stateful_service(target, self, event_details, self.logger())
})
}

View File

@@ -92,13 +92,13 @@ impl<'a> MySQL<'a> {
}
}
impl StatefulService for MySQL {
impl<'a> StatefulService for MySQL<'a> {
fn is_managed_service(&self) -> bool {
self.options.mode == MANAGED
}
}
impl ToTransmitter for MySQL {
impl<'a> ToTransmitter for MySQL<'a> {
fn to_transmitter(&self) -> Transmitter {
Transmitter::Database(
self.id().to_string(),
@@ -108,7 +108,7 @@ impl ToTransmitter for MySQL {
}
}
impl Service for MySQL {
impl<'a> Service for MySQL<'a> {
fn context(&self) -> &Context {
&self.context
}
@@ -190,7 +190,9 @@ impl Service for MySQL {
context.insert("namespace", environment.namespace());
let version = &self.matching_correct_version(self.is_managed_service(), event_details.clone())?;
let version = &self
.matching_correct_version(self.is_managed_service(), event_details.clone())?
.matched_version();
context.insert("version", &version);
if self.is_managed_service() {
@@ -261,9 +263,9 @@ impl Service for MySQL {
}
}
impl Database for MySQL {}
impl<'a> Database for MySQL<'a> {}
impl Helm for MySQL {
impl<'a> Helm for MySQL<'a> {
fn helm_selector(&self) -> Option<String> {
self.selector()
}
@@ -285,7 +287,7 @@ impl Helm for MySQL {
}
}
impl Terraform for MySQL {
impl<'a> Terraform for MySQL<'a> {
fn terraform_common_resource_dir_path(&self) -> String {
format!("{}/aws/services/common", self.context.lib_root_dir())
}
@@ -295,7 +297,7 @@ impl Terraform for MySQL {
}
}
impl Create for MySQL {
impl<'a> Create for MySQL<'a> {
#[named]
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
@@ -304,12 +306,12 @@ impl Create for MySQL {
self.struct_name(),
function_name!(),
self.name(),
event_details,
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || {
deploy_stateful_service(target, self, event_details.clone(), self.logger)
deploy_stateful_service(target, self, event_details, self.logger())
})
}
@@ -333,7 +335,7 @@ impl Create for MySQL {
}
}
impl Pause for MySQL {
impl<'a> Pause for MySQL<'a> {
#[named]
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
@@ -370,7 +372,7 @@ impl Pause for MySQL {
}
}
impl Delete for MySQL {
impl<'a> Delete for MySQL<'a> {
#[named]
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
@@ -384,7 +386,7 @@ impl Delete for MySQL {
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || {
delete_stateful_service(target, self, event_details, self.logger)
delete_stateful_service(target, self, event_details, self.logger())
})
}
@@ -407,7 +409,7 @@ impl Delete for MySQL {
}
}
impl Listen for MySQL {
impl<'a> Listen for MySQL<'a> {
fn listeners(&self) -> &Listeners {
&self.listeners
}

View File

@@ -190,7 +190,9 @@ impl<'a> Service for PostgreSQL<'a> {
context.insert("namespace", environment.namespace());
let version = self.matching_correct_version(self.is_managed_service(), event_details.clone())?;
let version = self
.matching_correct_version(self.is_managed_service(), event_details.clone())?
.matched_version();
context.insert("version", &version);
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
@@ -294,7 +296,7 @@ impl<'a> Create for PostgreSQL<'a> {
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || {
deploy_stateful_service(target, self, event_details, self.logger)
deploy_stateful_service(target, self, event_details, self.logger())
})
}
@@ -370,7 +372,7 @@ impl<'a> Delete for PostgreSQL<'a> {
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || {
delete_stateful_service(target, self, event_details, self.logger)
delete_stateful_service(target, self, event_details, self.logger())
})
}
@@ -386,7 +388,7 @@ impl<'a> Delete for PostgreSQL<'a> {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
event_details,
self.logger(),
);

View File

@@ -1,5 +1,4 @@
use crate::cloud_provider::utilities::VersionsNumber;
use crate::error::StringError;
use crate::errors::CommandError;
use crate::models::DatabaseKind;
use std::str::FromStr;

View File

@@ -5,9 +5,11 @@ use rusoto_credential::StaticProvider;
use rusoto_sts::{GetCallerIdentityRequest, Sts, StsClient};
use uuid::Uuid;
use crate::cloud_provider::{CloudProvider, EngineError, Kind, TerraformStateCredentials};
use crate::cloud_provider::{CloudProvider, Kind, TerraformStateCredentials};
use crate::constants::{AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY};
use crate::models::{Context, Listen, Listener, Listeners};
use crate::errors::EngineError;
use crate::events::{EventDetails, GeneralStep, Stage};
use crate::models::{Context, Listen, Listener, Listeners, QoveryIdentifier};
use crate::runtime::block_on;
pub mod application;
@@ -107,18 +109,15 @@ impl CloudProvider for AWS {
}
fn is_valid(&self) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::General(GeneralStep::RetrieveClusterConfig));
let client = StsClient::new_with_client(self.client(), Region::default());
let s = block_on(client.get_caller_identity(GetCallerIdentityRequest::default()));
match s {
Ok(_x) => Ok(()),
Err(_) => {
return Err(self.engine_error(
EngineErrorCause::User(
"Your AWS account seems to be no longer valid (bad Credentials). \
Please contact your Organization administrator to fix or change the Credentials.",
),
format!("failed to login to AWS {}", self.name_with_id()),
return Err(EngineError::new_client_invalid_cloud_provider_credentials(
event_details,
));
}
}
@@ -149,6 +148,19 @@ impl CloudProvider for AWS {
fn as_any(&self) -> &dyn Any {
self
}
fn get_event_details(&self, stage: Stage) -> EventDetails {
let context = self.context();
EventDetails::new(
None,
QoveryIdentifier::from(context.organization_id().to_string()),
QoveryIdentifier::from(context.cluster_id().to_string()),
QoveryIdentifier::from(context.execution_id().to_string()),
None,
stage,
self.to_transmitter(),
)
}
}
impl Listen for AWS {

View File

@@ -192,7 +192,15 @@ impl<'a> Service for Router<'a> {
Some(hostname) => context.insert("external_ingress_hostname_default", hostname.as_str()),
None => {
// TODO(benjaminch): Handle better this one via a proper error eventually
self.logger().log(LogLevel::Warning, EngineEvent::Warning(event_details.clone(), EventMessage::new_from_safe("unable to get external_ingress_hostname_default - what's wrong? This must never happened".to_string())));
self.logger().log(
LogLevel::Warning,
EngineEvent::Warning(
event_details.clone(),
EventMessage::new_from_safe(
"Error while trying to get Load Balancer hostname from Kubernetes cluster".to_string(),
),
),
);
}
},
_ => {
@@ -202,9 +210,7 @@ impl<'a> Service for Router<'a> {
LogLevel::Warning,
EngineEvent::Warning(
event_details.clone(),
EventMessage::new_from_safe(
"can't fetch kubernetes config file - what's wrong? This must never happened".to_string(),
),
EventMessage::new_from_safe("Can't fetch external ingress hostname.".to_string()),
),
);
}

View File

@@ -1,6 +1,7 @@
use tera::Context as TeraContext;
use crate::build_platform::Image;
use crate::cloud_provider::kubernetes::validate_k8s_required_cpu_and_burstable;
use crate::cloud_provider::models::{
EnvironmentVariable, EnvironmentVariableDataTemplate, Storage, StorageDataTemplate,
};
@@ -14,13 +15,14 @@ use crate::cloud_provider::DeploymentTarget;
use crate::cmd::helm::Timeout;
use crate::cmd::kubectl::ScalingKind::{Deployment, Statefulset};
use crate::errors::{CommandError, EngineError};
use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter};
use crate::events::{EngineEvent, EnvironmentStep, EventMessage, Stage, ToTransmitter, Transmitter};
use crate::logger::{LogLevel, Logger};
use crate::models::{Context, Listen, Listener, Listeners, ListenersHelper, Port};
use ::function_name::named;
use std::fmt;
use std::str::FromStr;
pub struct Application {
pub struct Application<'a> {
context: Context,
id: String,
action: Action,
@@ -36,9 +38,10 @@ pub struct Application {
storage: Vec<Storage<StorageType>>,
environment_variables: Vec<EnvironmentVariable>,
listeners: Listeners,
logger: &'a dyn Logger,
}
impl Application {
impl<'a> Application<'a> {
pub fn new(
context: Context,
id: &str,
@@ -55,6 +58,7 @@ impl Application {
storage: Vec<Storage<StorageType>>,
environment_variables: Vec<EnvironmentVariable>,
listeners: Listeners,
logger: &dyn Logger,
) -> Self {
Application {
context,
@@ -72,6 +76,7 @@ impl Application {
storage,
environment_variables,
listeners,
logger,
}
}
@@ -88,7 +93,7 @@ impl Application {
}
}
impl crate::cloud_provider::service::Application for Application {
impl<'a> crate::cloud_provider::service::Application for Application<'a> {
fn image(&self) -> &Image {
&self.image
}
@@ -98,7 +103,7 @@ impl crate::cloud_provider::service::Application for Application {
}
}
impl Helm for Application {
impl<'a> Helm for Application<'a> {
fn helm_selector(&self) -> Option<String> {
self.selector()
}
@@ -120,15 +125,15 @@ impl Helm for Application {
}
}
impl StatelessService for Application {}
impl<'a> StatelessService for Application<'a> {}
impl ToTransmitter for Application {
impl<'a> ToTransmitter for Application<'a> {
fn to_transmitter(&self) -> Transmitter {
Transmitter::Application(self.id().to_string(), self.name().to_string())
}
}
impl Service for Application {
impl<'a> Service for Application<'a> {
fn context(&self) -> &Context {
&self.context
}
@@ -193,6 +198,7 @@ impl Service for Application {
}
fn tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration));
let kubernetes = target.kubernetes;
let environment = target.environment;
let mut context = default_tera_context(self, kubernetes, environment);
@@ -204,10 +210,18 @@ impl Service for Application {
Some(registry_url) => context.insert("image_name_with_tag", registry_url.as_str()),
None => {
let image_name_with_tag = self.image.name_with_tag();
warn!(
"there is no registry url, use image name with tag with the default container registry: {}",
image_name_with_tag.as_str()
self.logger().log(
LogLevel::Warning,
EngineEvent::Warning(
event_details.clone(),
EventMessage::new_from_safe(format!(
"there is no registry url, use image name with tag with the default container registry: {}",
image_name_with_tag.as_str()
)),
),
);
context.insert("image_name_with_tag", image_name_with_tag.as_str());
}
}
@@ -218,14 +232,16 @@ impl Service for Application {
&self.id,
self.total_cpus(),
self.cpu_burst(),
event_details.clone(),
self.logger(),
) {
Ok(l) => l,
Err(e) => {
return Err(EngineError::new(
Internal,
EngineErrorScope::Application(self.id().to_string(), self.name().to_string()),
self.context.execution_id(),
Some(e.to_string()),
return Err(EngineError::new_k8s_validate_required_cpu_and_burstable_error(
event_details.clone(),
self.total_cpus(),
self.cpu_burst(),
e,
));
}
};
@@ -286,12 +302,16 @@ impl Service for Application {
Ok(context)
}
fn logger(&self) -> &dyn Logger {
self.logger
}
fn selector(&self) -> Option<String> {
Some(format!("appId={}", self.id))
}
}
impl Create for Application {
impl<'a> Create for Application<'a> {
#[named]
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
@@ -300,10 +320,12 @@ impl Create for Application {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || {
deploy_user_stateless_service(target, self, event_details.clone(), self.logger())
deploy_user_stateless_service(target, self, event_details)
})
}
@@ -319,15 +341,17 @@ impl Create for Application {
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || {
deploy_stateless_service_error(target, self, event_details.clone(), self.logger())
deploy_stateless_service_error(target, self)
})
}
}
impl Pause for Application {
impl<'a> Pause for Application<'a> {
#[named]
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
@@ -336,6 +360,8 @@ impl Pause for Application {
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || {
@@ -354,18 +380,21 @@ impl Pause for Application {
#[named]
fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Delete for Application {
impl<'a> Delete for Application<'a> {
#[named]
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
@@ -374,10 +403,12 @@ impl Delete for Application {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || {
delete_stateless_service(target, self, false, event_details.clone())
delete_stateless_service(target, self, false, event_details)
})
}
@@ -393,15 +424,17 @@ impl Delete for Application {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || {
delete_stateless_service(target, self, true, event_details.clone())
delete_stateless_service(target, self, true, event_details)
})
}
}
impl Listen for Application {
impl<'a> Listen for Application<'a> {
fn listeners(&self) -> &Listeners {
&self.listeners
}

View File

@@ -3,19 +3,20 @@ use tera::Context as TeraContext;
use crate::cloud_provider::service::{
check_service_version, default_tera_context, delete_stateful_service, deploy_stateful_service, get_tfstate_name,
get_tfstate_suffix, scale_down_database, send_progress_on_long_task, Action, Create, Database, DatabaseOptions,
DatabaseType, Delete, Helm, Pause, Service, ServiceType, StatefulService, Terraform,
DatabaseType, Delete, Helm, Pause, Service, ServiceType, ServiceVersionCheckResult, StatefulService, Terraform,
};
use crate::cloud_provider::utilities::{get_self_hosted_mongodb_version, print_action, sanitize_name};
use crate::cloud_provider::DeploymentTarget;
use crate::cmd::helm::Timeout;
use crate::cmd::kubectl;
use crate::error::{EngineError, EngineErrorScope};
use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter};
use crate::errors::EngineError;
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter};
use crate::logger::Logger;
use crate::models::DatabaseMode::MANAGED;
use crate::models::{Context, Listen, Listener, Listeners};
use ::function_name::named;
pub struct MongoDB {
pub struct MongoDB<'a> {
context: Context,
id: String,
action: Action,
@@ -28,9 +29,10 @@ pub struct MongoDB {
database_instance_type: String,
options: DatabaseOptions,
listeners: Listeners,
logger: &'a dyn Logger,
}
impl MongoDB {
impl<'a> MongoDB<'a> {
pub fn new(
context: Context,
id: &str,
@@ -44,6 +46,7 @@ impl MongoDB {
database_instance_type: &str,
options: DatabaseOptions,
listeners: Listeners,
logger: &'a dyn Logger,
) -> Self {
MongoDB {
context,
@@ -58,11 +61,16 @@ impl MongoDB {
database_instance_type: database_instance_type.to_string(),
options,
listeners,
logger,
}
}
fn matching_correct_version(&self) -> Result<String, EngineError> {
check_service_version(get_self_hosted_mongodb_version(self.version().clone()), self)
fn matching_correct_version(&self, event_details: EventDetails) -> Result<ServiceVersionCheckResult, EngineError> {
check_service_version(
get_self_hosted_mongodb_version(self.version().clone()),
self,
event_details,
)
}
fn cloud_provider_name(&self) -> &str {
@@ -74,13 +82,13 @@ impl MongoDB {
}
}
impl StatefulService for MongoDB {
impl<'a> StatefulService for MongoDB<'a> {
fn is_managed_service(&self) -> bool {
self.options.mode == MANAGED
}
}
impl ToTransmitter for MongoDB {
impl<'a> ToTransmitter for MongoDB<'a> {
fn to_transmitter(&self) -> Transmitter {
Transmitter::Database(
self.id().to_string(),
@@ -90,7 +98,7 @@ impl ToTransmitter for MongoDB {
}
}
impl Service for MongoDB {
impl<'a> Service for MongoDB<'a> {
fn context(&self) -> &Context {
&self.context
}
@@ -152,17 +160,13 @@ impl Service for MongoDB {
}
fn tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration));
let kubernetes = target.kubernetes;
let environment = target.environment;
let mut context = default_tera_context(self, kubernetes, environment);
// we need the kubernetes config file to store tfstates file in kube secrets
let kube_config_file_path = match kubernetes.get_kubeconfig_file_path() {
Ok(path) => path,
Err(e) => {
return Err(e.to_legacy_engine_error());
}
};
let kube_config_file_path = kubernetes.get_kubeconfig_file_path()?;
context.insert("kubeconfig_path", &kube_config_file_path);
kubectl::kubectl_exec_create_namespace_without_labels(
@@ -173,7 +177,7 @@ impl Service for MongoDB {
context.insert("namespace", environment.namespace());
let version = self.matching_correct_version()?;
let version = self.matching_correct_version(event_details.clone())?.matched_version();
context.insert("version", &version);
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
@@ -218,18 +222,14 @@ impl Service for MongoDB {
Some(format!("app={}", self.sanitized_name()))
}
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::Database(
self.id().to_string(),
self.service_type().name().to_string(),
self.name().to_string(),
)
fn logger(&self) -> &dyn Logger {
self.logger
}
}
impl Database for MongoDB {}
impl<'a> Database for MongoDB<'a> {}
impl Helm for MongoDB {
impl<'a> Helm for MongoDB<'a> {
fn helm_selector(&self) -> Option<String> {
self.selector()
}
@@ -251,7 +251,7 @@ impl Helm for MongoDB {
}
}
impl Terraform for MongoDB {
impl<'a> Terraform for MongoDB<'a> {
fn terraform_common_resource_dir_path(&self) -> String {
format!("{}/digitalocean/services/common", self.context.lib_root_dir())
}
@@ -261,7 +261,7 @@ impl Terraform for MongoDB {
}
}
impl Create for MongoDB {
impl<'a> Create for MongoDB<'a> {
#[named]
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
@@ -270,10 +270,12 @@ impl Create for MongoDB {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || {
deploy_stateful_service(target, self, event_details.clone())
deploy_stateful_service(target, self, event_details, self.logger())
})
}
@@ -283,25 +285,31 @@ impl Create for MongoDB {
#[named]
fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Pause for MongoDB {
impl<'a> Pause for MongoDB<'a> {
#[named]
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || {
@@ -315,18 +323,21 @@ impl Pause for MongoDB {
#[named]
fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Delete for MongoDB {
impl<'a> Delete for MongoDB<'a> {
#[named]
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
@@ -335,10 +346,12 @@ impl Delete for MongoDB {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || {
delete_stateful_service(target, self, event_details.clone())
delete_stateful_service(target, self, event_details, self.logger())
})
}
@@ -348,18 +361,21 @@ impl Delete for MongoDB {
#[named]
fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Listen for MongoDB {
impl<'a> Listen for MongoDB<'a> {
fn listeners(&self) -> &Listeners {
&self.listeners
}

View File

@@ -3,19 +3,20 @@ use tera::Context as TeraContext;
use crate::cloud_provider::service::{
check_service_version, default_tera_context, delete_stateful_service, deploy_stateful_service, get_tfstate_name,
get_tfstate_suffix, scale_down_database, send_progress_on_long_task, Action, Create, Database, DatabaseOptions,
DatabaseType, Delete, Helm, Pause, Service, ServiceType, StatefulService, Terraform,
DatabaseType, Delete, Helm, Pause, Service, ServiceType, ServiceVersionCheckResult, StatefulService, Terraform,
};
use crate::cloud_provider::utilities::{get_self_hosted_mysql_version, print_action, sanitize_name};
use crate::cloud_provider::DeploymentTarget;
use crate::cmd::helm::Timeout;
use crate::cmd::kubectl;
use crate::error::{EngineError, EngineErrorScope};
use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter};
use crate::errors::EngineError;
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter};
use crate::logger::Logger;
use crate::models::DatabaseMode::MANAGED;
use crate::models::{Context, Listen, Listener, Listeners};
use ::function_name::named;
pub struct MySQL {
pub struct MySQL<'a> {
context: Context,
id: String,
action: Action,
@@ -28,9 +29,10 @@ pub struct MySQL {
database_instance_type: String,
options: DatabaseOptions,
listeners: Listeners,
logger: &'a dyn Logger,
}
impl MySQL {
impl<'a> MySQL<'a> {
pub fn new(
context: Context,
id: &str,
@@ -44,6 +46,7 @@ impl MySQL {
database_instance_type: &str,
options: DatabaseOptions,
listeners: Listeners,
logger: &'a dyn Logger,
) -> Self {
Self {
context,
@@ -58,11 +61,12 @@ impl MySQL {
database_instance_type: database_instance_type.to_string(),
options,
listeners,
logger,
}
}
fn matching_correct_version(&self) -> Result<String, EngineError> {
check_service_version(get_self_hosted_mysql_version(self.version()), self)
fn matching_correct_version(&self, event_details: EventDetails) -> Result<ServiceVersionCheckResult, EngineError> {
check_service_version(get_self_hosted_mysql_version(self.version()), self, event_details)
}
fn cloud_provider_name(&self) -> &str {
@@ -74,13 +78,13 @@ impl MySQL {
}
}
impl StatefulService for MySQL {
impl<'a> StatefulService for MySQL<'a> {
fn is_managed_service(&self) -> bool {
self.options.mode == MANAGED
}
}
impl ToTransmitter for MySQL {
impl<'a> ToTransmitter for MySQL<'a> {
fn to_transmitter(&self) -> Transmitter {
Transmitter::Database(
self.id().to_string(),
@@ -90,7 +94,7 @@ impl ToTransmitter for MySQL {
}
}
impl Service for MySQL {
impl<'a> Service for MySQL<'a> {
fn context(&self) -> &Context {
&self.context
}
@@ -152,17 +156,13 @@ impl Service for MySQL {
}
fn tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration));
let kubernetes = target.kubernetes;
let environment = target.environment;
let mut context = default_tera_context(self, kubernetes, environment);
// we need the kubernetes config file to store tfstates file in kube secrets
let kube_config_file_path = match kubernetes.get_kubeconfig_file_path() {
Ok(path) => path,
Err(e) => {
return Err(e.to_legacy_engine_error());
}
};
let kube_config_file_path = kubernetes.get_kubeconfig_file_path()?;
context.insert("kubeconfig_path", &kube_config_file_path);
kubectl::kubectl_exec_create_namespace_without_labels(
@@ -173,7 +173,7 @@ impl Service for MySQL {
context.insert("namespace", environment.namespace());
let version = &self.matching_correct_version()?;
let version = &self.matching_correct_version(event_details.clone())?.matched_version();
context.insert("version", &version);
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
@@ -217,19 +217,11 @@ impl Service for MySQL {
fn selector(&self) -> Option<String> {
Some(format!("app={}", self.sanitized_name()))
}
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::Database(
self.id().to_string(),
self.service_type().name().to_string(),
self.name().to_string(),
)
}
}
impl Database for MySQL {}
impl<'a> Database for MySQL<'a> {}
impl Helm for MySQL {
impl<'a> Helm for MySQL<'a> {
fn helm_selector(&self) -> Option<String> {
self.selector()
}
@@ -251,7 +243,7 @@ impl Helm for MySQL {
}
}
impl Terraform for MySQL {
impl<'a> Terraform for MySQL<'a> {
fn terraform_common_resource_dir_path(&self) -> String {
format!("{}/digitalocean/services/common", self.context.lib_root_dir())
}
@@ -261,7 +253,7 @@ impl Terraform for MySQL {
}
}
impl Create for MySQL {
impl<'a> Create for MySQL<'a> {
#[named]
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
@@ -270,12 +262,14 @@ impl Create for MySQL {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(
self,
crate::cloud_provider::service::Action::Create,
Box::new(|| deploy_stateful_service(target, self, event_details.clone())),
Box::new(|| deploy_stateful_service(target, self, event_details, self.logger())),
)
}
@@ -286,25 +280,31 @@ impl Create for MySQL {
#[named]
fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Pause for MySQL {
impl<'a> Pause for MySQL<'a> {
#[named]
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || {
@@ -318,18 +318,21 @@ impl Pause for MySQL {
#[named]
fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Delete for MySQL {
impl<'a> Delete for MySQL<'a> {
#[named]
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
@@ -338,12 +341,14 @@ impl Delete for MySQL {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(
self,
crate::cloud_provider::service::Action::Delete,
Box::new(|| delete_stateful_service(target, self, event_details.clone())),
Box::new(|| delete_stateful_service(target, self, event_details, self.logger())),
)
}
@@ -353,17 +358,20 @@ impl Delete for MySQL {
#[named]
fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Listen for MySQL {
impl<'a> Listen for MySQL<'a> {
fn listeners(&self) -> &Listeners {
&self.listeners
}

View File

@@ -3,19 +3,20 @@ use tera::Context as TeraContext;
use crate::cloud_provider::service::{
check_service_version, default_tera_context, delete_stateful_service, deploy_stateful_service, get_tfstate_name,
get_tfstate_suffix, scale_down_database, send_progress_on_long_task, Action, Create, Database, DatabaseOptions,
DatabaseType, Delete, Helm, Pause, Service, ServiceType, StatefulService, Terraform,
DatabaseType, Delete, Helm, Pause, Service, ServiceType, ServiceVersionCheckResult, StatefulService, Terraform,
};
use crate::cloud_provider::utilities::{get_self_hosted_postgres_version, print_action, sanitize_name};
use crate::cloud_provider::DeploymentTarget;
use crate::cmd::helm::Timeout;
use crate::cmd::kubectl;
use crate::error::{EngineError, EngineErrorScope};
use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter};
use crate::errors::EngineError;
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter};
use crate::logger::Logger;
use crate::models::DatabaseMode::MANAGED;
use crate::models::{Context, Listen, Listener, Listeners};
use ::function_name::named;
pub struct PostgreSQL {
pub struct PostgreSQL<'a> {
context: Context,
id: String,
action: Action,
@@ -28,9 +29,10 @@ pub struct PostgreSQL {
database_instance_type: String,
options: DatabaseOptions,
listeners: Listeners,
logger: &'a dyn Logger,
}
impl PostgreSQL {
impl<'a> PostgreSQL<'a> {
pub fn new(
context: Context,
id: &str,
@@ -44,6 +46,7 @@ impl PostgreSQL {
database_instance_type: &str,
options: DatabaseOptions,
listeners: Listeners,
logger: &dyn Logger,
) -> Self {
PostgreSQL {
context,
@@ -58,11 +61,12 @@ impl PostgreSQL {
database_instance_type: database_instance_type.to_string(),
options,
listeners,
logger,
}
}
fn matching_correct_version(&self) -> Result<String, EngineError> {
check_service_version(get_self_hosted_postgres_version(self.version()), self)
fn matching_correct_version(&self, event_details: EventDetails) -> Result<ServiceVersionCheckResult, EngineError> {
check_service_version(get_self_hosted_postgres_version(self.version()), self, event_details)
}
fn cloud_provider_name(&self) -> &str {
@@ -74,13 +78,13 @@ impl PostgreSQL {
}
}
impl StatefulService for PostgreSQL {
impl<'a> StatefulService for PostgreSQL<'a> {
fn is_managed_service(&self) -> bool {
self.options.mode == MANAGED
}
}
impl ToTransmitter for PostgreSQL {
impl<'a> ToTransmitter for PostgreSQL<'a> {
fn to_transmitter(&self) -> Transmitter {
Transmitter::Database(
self.id().to_string(),
@@ -90,7 +94,7 @@ impl ToTransmitter for PostgreSQL {
}
}
impl Service for PostgreSQL {
impl<'a> Service for PostgreSQL<'a> {
fn context(&self) -> &Context {
&self.context
}
@@ -152,17 +156,13 @@ impl Service for PostgreSQL {
}
fn tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration));
let kubernetes = target.kubernetes;
let environment = target.environment;
let mut context = default_tera_context(self, kubernetes, environment);
// we need the kubernetes config file to store tfstates file in kube secrets
let kube_config_file_path = match kubernetes.get_kubeconfig_file_path() {
Ok(path) => path,
Err(e) => {
return Err(e.to_legacy_engine_error());
}
};
let kube_config_file_path = kubernetes.get_kubeconfig_file_path()?;
context.insert("kubeconfig_path", &kube_config_file_path);
kubectl::kubectl_exec_create_namespace_without_labels(
@@ -173,7 +173,7 @@ impl Service for PostgreSQL {
context.insert("namespace", environment.namespace());
let version = self.matching_correct_version()?;
let version = self.matching_correct_version(event_details.clone())?.matched_version();
context.insert("version", &version);
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
@@ -220,18 +220,14 @@ impl Service for PostgreSQL {
Some(format!("app={}", self.sanitized_name()))
}
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::Database(
self.id().to_string(),
self.service_type().name().to_string(),
self.name().to_string(),
)
fn logger(&self) -> &dyn Logger {
self.logger
}
}
impl Database for PostgreSQL {}
impl<'a> Database for PostgreSQL<'a> {}
impl Helm for PostgreSQL {
impl<'a> Helm for PostgreSQL<'a> {
fn helm_selector(&self) -> Option<String> {
self.selector()
}
@@ -253,7 +249,7 @@ impl Helm for PostgreSQL {
}
}
impl Terraform for PostgreSQL {
impl<'a> Terraform for PostgreSQL<'a> {
fn terraform_common_resource_dir_path(&self) -> String {
format!("{}/digitalocean/services/common", self.context.lib_root_dir())
}
@@ -263,7 +259,7 @@ impl Terraform for PostgreSQL {
}
}
impl Create for PostgreSQL {
impl<'a> Create for PostgreSQL<'a> {
#[named]
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
@@ -272,12 +268,14 @@ impl Create for PostgreSQL {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(
self,
crate::cloud_provider::service::Action::Create,
Box::new(|| deploy_stateful_service(target, self, event_details.clone())),
Box::new(|| deploy_stateful_service(target, self, event_details, self.logger())),
)
}
@@ -287,25 +285,31 @@ impl Create for PostgreSQL {
#[named]
fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Pause for PostgreSQL {
impl<'a> Pause for PostgreSQL<'a> {
#[named]
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || {
@@ -319,18 +323,21 @@ impl Pause for PostgreSQL {
#[named]
fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Delete for PostgreSQL {
impl<'a> Delete for PostgreSQL<'a> {
#[named]
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
@@ -339,12 +346,14 @@ impl Delete for PostgreSQL {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(
self,
crate::cloud_provider::service::Action::Delete,
Box::new(|| delete_stateful_service(target, self, event_details.clone())),
Box::new(|| delete_stateful_service(target, self, event_details, self.logger())),
)
}
@@ -354,18 +363,21 @@ impl Delete for PostgreSQL {
#[named]
fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Listen for PostgreSQL {
impl<'a> Listen for PostgreSQL<'a> {
fn listeners(&self) -> &Listeners {
&self.listeners
}

View File

@@ -3,19 +3,20 @@ use tera::Context as TeraContext;
use crate::cloud_provider::service::{
check_service_version, default_tera_context, delete_stateful_service, deploy_stateful_service, get_tfstate_name,
get_tfstate_suffix, scale_down_database, send_progress_on_long_task, Action, Create, Database, DatabaseOptions,
DatabaseType, Delete, Helm, Pause, Service, ServiceType, StatefulService, Terraform,
DatabaseType, Delete, Helm, Pause, Service, ServiceType, ServiceVersionCheckResult, StatefulService, Terraform,
};
use crate::cloud_provider::utilities::{get_self_hosted_redis_version, print_action, sanitize_name};
use crate::cloud_provider::DeploymentTarget;
use crate::cmd::helm::Timeout;
use crate::cmd::kubectl;
use crate::error::{EngineError, EngineErrorScope};
use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter};
use crate::errors::EngineError;
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter};
use crate::logger::Logger;
use crate::models::DatabaseMode::MANAGED;
use crate::models::{Context, Listen, Listener, Listeners};
use ::function_name::named;
pub struct Redis {
pub struct Redis<'a> {
context: Context,
id: String,
action: Action,
@@ -28,9 +29,10 @@ pub struct Redis {
database_instance_type: String,
options: DatabaseOptions,
listeners: Listeners,
logger: &'a dyn Logger,
}
impl Redis {
impl<'a> Redis<'a> {
pub fn new(
context: Context,
id: &str,
@@ -44,6 +46,7 @@ impl Redis {
database_instance_type: &str,
options: DatabaseOptions,
listeners: Listeners,
logger: &'a dyn Logger,
) -> Self {
Self {
context,
@@ -58,11 +61,12 @@ impl Redis {
database_instance_type: database_instance_type.to_string(),
options,
listeners,
logger,
}
}
fn matching_correct_version(&self) -> Result<String, EngineError> {
check_service_version(get_self_hosted_redis_version(self.version()), self)
fn matching_correct_version(&self, event_details: EventDetails) -> Result<ServiceVersionCheckResult, EngineError> {
check_service_version(get_self_hosted_redis_version(self.version()), self, event_details)
}
fn cloud_provider_name(&self) -> &str {
@@ -74,13 +78,13 @@ impl Redis {
}
}
impl StatefulService for Redis {
impl<'a> StatefulService for Redis<'a> {
fn is_managed_service(&self) -> bool {
self.options.mode == MANAGED
}
}
impl ToTransmitter for Redis {
impl<'a> ToTransmitter for Redis<'a> {
fn to_transmitter(&self) -> Transmitter {
Transmitter::Database(
self.id().to_string(),
@@ -90,7 +94,7 @@ impl ToTransmitter for Redis {
}
}
impl Service for Redis {
impl<'a> Service for Redis<'a> {
fn context(&self) -> &Context {
&self.context
}
@@ -152,17 +156,13 @@ impl Service for Redis {
}
fn tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration));
let kubernetes = target.kubernetes;
let environment = target.environment;
let mut context = default_tera_context(self, kubernetes, environment);
// we need the kubernetes config file to store tfstates file in kube secrets
let kube_config_file_path = match kubernetes.get_kubeconfig_file_path() {
Ok(path) => path,
Err(e) => {
return Err(e.to_legacy_engine_error());
}
};
let kube_config_file_path = kubernetes.get_kubeconfig_file_path()?;
context.insert("kubeconfig_path", &kube_config_file_path);
kubectl::kubectl_exec_create_namespace_without_labels(
@@ -171,7 +171,7 @@ impl Service for Redis {
kubernetes.cloud_provider().credentials_environment_variables(),
);
let version = self.matching_correct_version()?;
let version = self.matching_correct_version(event_details.clone())?.matched_version();
context.insert("namespace", environment.namespace());
context.insert("version", &version);
@@ -217,18 +217,14 @@ impl Service for Redis {
Some(format!("app={}", self.sanitized_name()))
}
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::Database(
self.id().to_string(),
self.service_type().name().to_string(),
self.name().to_string(),
)
fn logger(&self) -> &dyn Logger {
self.logger
}
}
impl Database for Redis {}
impl<'a> Database for Redis<'a> {}
impl Helm for Redis {
impl<'a> Helm for Redis<'a> {
fn helm_selector(&self) -> Option<String> {
self.selector()
}
@@ -250,7 +246,7 @@ impl Helm for Redis {
}
}
impl Terraform for Redis {
impl<'a> Terraform for Redis<'a> {
fn terraform_common_resource_dir_path(&self) -> String {
format!("{}/digitalocean/services/common", self.context.lib_root_dir())
}
@@ -260,7 +256,7 @@ impl Terraform for Redis {
}
}
impl Create for Redis {
impl<'a> Create for Redis<'a> {
#[named]
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
@@ -269,12 +265,14 @@ impl Create for Redis {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(
self,
crate::cloud_provider::service::Action::Create,
Box::new(|| deploy_stateful_service(target, self, event_details.clone())),
Box::new(|| deploy_stateful_service(target, self, event_details, self.logger())),
)
}
@@ -285,24 +283,30 @@ impl Create for Redis {
#[named]
fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Pause for Redis {
impl<'a> Pause for Redis<'a> {
#[named]
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || {
@@ -316,17 +320,20 @@ impl Pause for Redis {
#[named]
fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Delete for Redis {
impl<'a> Delete for Redis<'a> {
#[named]
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
@@ -335,12 +342,14 @@ impl Delete for Redis {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(
self,
crate::cloud_provider::service::Action::Pause,
Box::new(|| delete_stateful_service(target, self, event_details.clone())),
Box::new(|| delete_stateful_service(target, self, event_details, self.logger())),
)
}
@@ -350,17 +359,20 @@ impl Delete for Redis {
#[named]
fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Listen for Redis {
impl<'a> Listen for Redis<'a> {
fn listeners(&self) -> &Listeners {
&self.listeners
}

View File

@@ -7,8 +7,9 @@ use uuid::Uuid;
use crate::cloud_provider::{CloudProvider, Kind, TerraformStateCredentials};
use crate::constants::DIGITAL_OCEAN_TOKEN;
use crate::error::{EngineError, EngineErrorCause};
use crate::models::{Context, Listen, Listener, Listeners};
use crate::errors::EngineError;
use crate::events::{EventDetails, GeneralStep, Stage};
use crate::models::{Context, Listen, Listener, Listeners, QoveryIdentifier};
pub mod application;
pub mod databases;
@@ -100,16 +101,13 @@ impl CloudProvider for DO {
}
fn is_valid(&self) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::General(GeneralStep::RetrieveClusterConfig));
let client = DigitalOcean::new(&self.token);
match client {
Ok(_x) => Ok(()),
Err(_) => {
return Err(self.engine_error(
EngineErrorCause::User(
"Your DigitalOcean account seems to be no longer valid (bad Credentials). \
Please contact your Organization administrator to fix or change the Credentials.",
),
format!("failed to login to Digital Ocean {}", self.name_with_id()),
return Err(EngineError::new_client_invalid_cloud_provider_credentials(
event_details,
));
}
}
@@ -134,6 +132,19 @@ impl CloudProvider for DO {
fn as_any(&self) -> &dyn Any {
self
}
fn get_event_details(&self, stage: Stage) -> EventDetails {
let context = self.context();
EventDetails::new(
None,
QoveryIdentifier::from(context.organization_id().to_string()),
QoveryIdentifier::from(context.cluster_id().to_string()),
QoveryIdentifier::from(context.execution_id().to_string()),
None,
stage,
self.to_transmitter(),
)
}
}
impl Listen for DO {

View File

@@ -3,35 +3,34 @@ extern crate serde_json;
use reqwest::StatusCode;
use crate::cloud_provider::digitalocean::models::load_balancers::LoadBalancer;
use crate::error::{SimpleError, SimpleErrorKind};
use crate::errors::CommandError;
use crate::utilities::get_header_with_bearer;
use std::net::Ipv4Addr;
use std::str::FromStr;
pub const DO_LOAD_BALANCER_API_PATH: &str = "https://api.digitalocean.com/v2/load_balancers";
pub fn get_ip_from_do_load_balancer_api_output(json_content: &str) -> Result<Ipv4Addr, SimpleError> {
pub fn get_ip_from_do_load_balancer_api_output(json_content: &str) -> Result<Ipv4Addr, CommandError> {
let res_load_balancer = serde_json::from_str::<LoadBalancer>(json_content);
match res_load_balancer {
Ok(lb) => match Ipv4Addr::from_str(&lb.load_balancer.ip) {
Ok(ip) => Ok(ip),
Err(e) => Err(SimpleError::new(
SimpleErrorKind::Other,
Err(e) => Err(CommandError::new(
e.to_string(),
Some(format!(
"Info returned from DO API is not a valid IP, received '{:?}' instead. {:?}",
&lb.load_balancer.ip, e
"Info returned from DO API is not a valid IP, received '{:?}' instead.",
&lb.load_balancer.ip,
)),
)),
},
Err(_) => Err(SimpleError::new(
SimpleErrorKind::Other,
Some("Error While trying to deserialize json received from Digital Ocean Load Balancer API".to_string()),
Err(_) => Err(CommandError::new_from_safe_message(
"Error While trying to deserialize json received from Digital Ocean Load Balancer API".to_string(),
)),
}
}
pub fn do_get_load_balancer_ip(token: &str, load_balancer_id: &str) -> Result<Ipv4Addr, SimpleError> {
pub fn do_get_load_balancer_ip(token: &str, load_balancer_id: &str) -> Result<Ipv4Addr, CommandError> {
let headers = get_header_with_bearer(token);
let url = format!("{}/{}", DO_LOAD_BALANCER_API_PATH, load_balancer_id);
let res = reqwest::blocking::Client::new().get(&url).headers(headers).send();
@@ -42,17 +41,16 @@ pub fn do_get_load_balancer_ip(token: &str, load_balancer_id: &str) -> Result<Ip
let content = response.text().unwrap();
get_ip_from_do_load_balancer_api_output(content.as_str())
}
_ => Err(SimpleError::new(
SimpleErrorKind::Other,
_ => Err(CommandError::new(
format!("{:?}", response),
Some(
format!("Unknown status code received from Digital Ocean Kubernetes API while retrieving load balancer information. {:?}", response),
"Unknown status code received from Digital Ocean Kubernetes API while retrieving load balancer information.".to_string(),
),
)),
},
Err(_) => {
Err(SimpleError::new(
SimpleErrorKind::Other,
Some("Unable to get a response from Digital Ocean Load Balancer API"),
Err(CommandError::new_from_safe_message(
"Unable to get a response from Digital Ocean Load Balancer API".to_string(),
))
}
};

View File

@@ -3,18 +3,18 @@ use tera::Context as TeraContext;
use crate::cloud_provider::models::{CustomDomain, CustomDomainDataTemplate, Route, RouteDataTemplate};
use crate::cloud_provider::service::{
default_tera_context, delete_router, deploy_stateless_service_error, send_progress_on_long_task, Action, Create,
Delete, Helm, Pause, Service, ServiceType, StatelessService,
Delete, Helm, Pause, Router as RRouter, Service, ServiceType, StatelessService,
};
use crate::cloud_provider::utilities::{check_cname_for, print_action, sanitize_name};
use crate::cloud_provider::DeploymentTarget;
use crate::cmd::helm::Timeout;
use crate::error::{EngineError, EngineErrorCause, EngineErrorScope};
use crate::errors::EngineError as NewEngineError;
use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter};
use crate::errors::EngineError;
use crate::events::{EngineEvent, EnvironmentStep, EventMessage, Stage, ToTransmitter, Transmitter};
use crate::logger::{LogLevel, Logger};
use crate::models::{Context, Listen, Listener, Listeners};
use ::function_name::named;
pub struct Router {
pub struct Router<'a> {
context: Context,
id: String,
action: Action,
@@ -24,9 +24,10 @@ pub struct Router {
sticky_sessions_enabled: bool,
routes: Vec<Route>,
listeners: Listeners,
logger: &'a dyn Logger,
}
impl Router {
impl<'a> Router<'a> {
pub fn new(
context: Context,
id: &str,
@@ -37,6 +38,7 @@ impl Router {
routes: Vec<Route>,
sticky_sessions_enabled: bool,
listeners: Listeners,
logger: &'a dyn Logger,
) -> Self {
Router {
context,
@@ -48,6 +50,7 @@ impl Router {
sticky_sessions_enabled,
routes,
listeners,
logger,
}
}
@@ -60,7 +63,7 @@ impl Router {
}
}
impl Service for Router {
impl<'a> Service for Router<'a> {
fn context(&self) -> &Context {
&self.context
}
@@ -122,6 +125,7 @@ impl Service for Router {
}
fn tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration));
let kubernetes = target.kubernetes;
let environment = target.environment;
let mut context = default_tera_context(self, kubernetes, environment);
@@ -185,34 +189,44 @@ impl Service for Router {
context.insert("nginx_requests_memory", "128Mi");
context.insert("nginx_limit_cpu", "200m");
context.insert("nginx_limit_memory", "128Mi");
let kubernetes_config_file_path = kubernetes.get_kubeconfig_file_path();
match kubernetes_config_file_path {
Ok(kubernetes_config_file_path_string) => {
// Default domain
let external_ingress_hostname_default = crate::cmd::kubectl::kubectl_exec_get_external_ingress_hostname(
kubernetes_config_file_path_string.as_str(),
"nginx-ingress",
"nginx-ingress-ingress-nginx-controller",
kubernetes.cloud_provider().credentials_environment_variables(),
);
let kubernetes_config_file_path = kubernetes.get_kubeconfig_file_path()?;
match external_ingress_hostname_default {
Ok(external_ingress_hostname_default) => match external_ingress_hostname_default {
Some(hostname) => context.insert("external_ingress_hostname_default", hostname.as_str()),
None => {
return Err(self.engine_error(
EngineErrorCause::Internal,
"Error while trying to get Load Balancer hostname from Kubernetes cluster".into(),
));
}
},
_ => {
error!("can't fetch external ingress hostname");
}
// Default domain
let external_ingress_hostname_default = crate::cmd::kubectl::kubectl_exec_get_external_ingress_hostname(
kubernetes_config_file_path,
"nginx-ingress",
"nginx-ingress-ingress-nginx-controller",
kubernetes.cloud_provider().credentials_environment_variables(),
);
match external_ingress_hostname_default {
Ok(external_ingress_hostname_default) => match external_ingress_hostname_default {
Some(hostname) => context.insert("external_ingress_hostname_default", hostname.as_str()),
None => {
// TODO(benjaminch): Handle better this one via a proper error eventually
self.logger().log(
LogLevel::Warning,
EngineEvent::Warning(
event_details.clone(),
EventMessage::new_from_safe(
"Error while trying to get Load Balancer hostname from Kubernetes cluster".to_string(),
),
),
);
}
},
_ => {
// FIXME really?
// TODO(benjaminch): Handle better this one via a proper error eventually
self.logger().log(
LogLevel::Warning,
EngineEvent::Warning(
event_details.clone(),
EventMessage::new_from_safe("Can't fetch external ingress hostname.".to_string()),
),
);
}
Err(_) => error!("can't fetch kubernetes config file - what's wrong? This must never happened"),
}
let router_default_domain_hash = crate::crypto::to_sha1_truncate_16(self.default_domain.as_str());
@@ -242,12 +256,12 @@ impl Service for Router {
Some(format!("routerId={}", self.id))
}
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::Router(self.id().to_string(), self.name().to_string())
fn logger(&self) -> &dyn Logger {
self.logger
}
}
impl crate::cloud_provider::service::Router for Router {
impl<'a> crate::cloud_provider::service::Router for Router<'a> {
fn domains(&self) -> Vec<&str> {
let mut _domains = vec![self.default_domain.as_str()];
@@ -263,7 +277,7 @@ impl crate::cloud_provider::service::Router for Router {
}
}
impl Helm for Router {
impl<'a> Helm for Router<'a> {
fn helm_selector(&self) -> Option<String> {
self.selector()
}
@@ -288,7 +302,7 @@ impl Helm for Router {
}
}
impl Listen for Router {
impl<'a> Listen for Router<'a> {
fn listeners(&self) -> &Listeners {
&self.listeners
}
@@ -298,34 +312,33 @@ impl Listen for Router {
}
}
impl StatelessService for Router {}
impl<'a> StatelessService for Router<'a> {}
impl ToTransmitter for Router {
impl<'a> ToTransmitter for Router<'a> {
fn to_transmitter(&self) -> Transmitter {
Transmitter::Router(self.id().to_string(), self.name().to_string())
}
}
impl Create for Router {
impl<'a> Create for Router<'a> {
#[named]
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
let kubernetes = target.kubernetes;
let environment = target.environment;
let workspace_dir = self.workspace_directory();
let helm_release_name = self.helm_release_name();
let kubernetes_config_file_path = match kubernetes.get_kubeconfig_file_path() {
Ok(p) => p,
Err(e) => return Err(e.to_legacy_engine_error()),
};
let kubernetes_config_file_path = kubernetes.get_kubeconfig_file_path()?;
// respect order - getting the context here and not before is mandatory
// the nginx-ingress must be available to get the external dns target if necessary
@@ -335,13 +348,12 @@ impl Create for Router {
if let Err(e) =
crate::template::generate_and_copy_all_files_into_dir(from_dir.as_str(), workspace_dir.as_str(), context)
{
return Err(NewEngineError::new_cannot_copy_files_from_one_directory_to_another(
return Err(EngineError::new_cannot_copy_files_from_one_directory_to_another(
event_details.clone(),
from_dir.to_string(),
workspace_dir.to_string(),
e,
)
.to_legacy_engine_error());
));
}
// do exec helm upgrade and return the last deployment status
@@ -355,19 +367,17 @@ impl Create for Router {
kubernetes.cloud_provider().credentials_environment_variables(),
self.service_type(),
)
.map_err(|e| {
NewEngineError::new_helm_charts_upgrade_error(event_details.clone(), e).to_legacy_engine_error()
})?;
.map_err(|e| EngineError::new_helm_charts_upgrade_error(event_details.clone(), e).to_legacy_engine_error())?;
if helm_history_row.is_none() || !helm_history_row.unwrap().is_successfully_deployed() {
return Err(self.engine_error(EngineErrorCause::Internal, "Router has failed to be deployed".into()));
return Err(EngineError::new_router_failed_to_deploy(event_details.clone()));
}
Ok(())
}
fn on_create_check(&self) -> Result<(), EngineError> {
use crate::cloud_provider::service::Router;
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
// check non custom domains
self.check_domains()?;
@@ -384,9 +394,19 @@ impl Create for Router {
continue;
}
Ok(err) | Err(err) => {
warn!(
"Invalid CNAME for {}. Might not be an issue if user is using a CDN: {}",
domain_to_check.domain, err
// TODO(benjaminch): Handle better this one via a proper error eventually
self.logger().log(
LogLevel::Warning,
EngineEvent::Warning(
event_details.clone(),
EventMessage::new(
format!(
"Invalid CNAME for {}. Might not be an issue if user is using a CDN.",
domain_to_check.domain,
),
Some(err.to_string()),
),
),
);
}
}
@@ -397,11 +417,14 @@ impl Create for Router {
#[named]
fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || {
@@ -410,14 +433,17 @@ impl Create for Router {
}
}
impl Pause for Router {
impl<'a> Pause for Router<'a> {
#[named]
fn on_pause(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
@@ -428,17 +454,20 @@ impl Pause for Router {
#[named]
fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Delete for Router {
impl<'a> Delete for Router<'a> {
#[named]
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
@@ -447,6 +476,8 @@ impl Delete for Router {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
delete_router(target, self, false, event_details)
}
@@ -463,6 +494,8 @@ impl Delete for Router {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
delete_router(target, self, true, event_details)
}

View File

@@ -5,7 +5,8 @@ use serde::{Deserialize, Serialize};
use crate::cloud_provider::environment::Environment;
use crate::cloud_provider::kubernetes::Kubernetes;
use crate::error::{EngineError, EngineErrorCause, EngineErrorScope};
use crate::errors::EngineError;
use crate::events::{EventDetails, Stage};
use crate::models::{Context, Listen};
pub mod aws;
@@ -41,18 +42,8 @@ pub trait CloudProvider: Listen {
/// environment variables to inject to generate Terraform files from templates
fn tera_context_environment_variables(&self) -> Vec<(&str, &str)>;
fn terraform_state_credentials(&self) -> &TerraformStateCredentials;
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::CloudProvider(self.id().to_string(), self.name().to_string())
}
fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError {
EngineError::new(
cause,
self.engine_error_scope(),
self.context().execution_id(),
Some(message),
)
}
fn as_any(&self) -> &dyn Any;
fn get_event_details(&self, stage: Stage) -> EventDetails;
}
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]

View File

@@ -4,6 +4,7 @@ use std::str::FromStr;
use tera::Context as TeraContext;
use crate::build_platform::Image;
use crate::cloud_provider::kubernetes::validate_k8s_required_cpu_and_burstable;
use crate::cloud_provider::models::{
EnvironmentVariable, EnvironmentVariableDataTemplate, Storage, StorageDataTemplate,
};
@@ -16,14 +17,13 @@ use crate::cloud_provider::utilities::{print_action, sanitize_name};
use crate::cloud_provider::DeploymentTarget;
use crate::cmd::helm::Timeout;
use crate::cmd::kubectl::ScalingKind::{Deployment, Statefulset};
use crate::error::EngineErrorCause::Internal;
use crate::error::{EngineError, EngineErrorScope};
use crate::errors::CommandError;
use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter};
use crate::errors::{CommandError, EngineError};
use crate::events::{EngineEvent, EnvironmentStep, EventMessage, Stage, ToTransmitter, Transmitter};
use crate::logger::{LogLevel, Logger};
use crate::models::{Context, Listen, Listener, Listeners, ListenersHelper, Port};
use ::function_name::named;
pub struct Application {
pub struct Application<'a> {
context: Context,
id: String,
action: Action,
@@ -39,9 +39,10 @@ pub struct Application {
storage: Vec<Storage<StorageType>>,
environment_variables: Vec<EnvironmentVariable>,
listeners: Listeners,
logger: &'a dyn Logger,
}
impl Application {
impl<'a> Application<'a> {
pub fn new(
context: Context,
id: &str,
@@ -58,7 +59,8 @@ impl Application {
storage: Vec<Storage<StorageType>>,
environment_variables: Vec<EnvironmentVariable>,
listeners: Listeners,
) -> Application {
logger: &dyn Logger,
) -> Self {
Application {
context,
id: id.to_string(),
@@ -75,6 +77,7 @@ impl Application {
storage,
environment_variables,
listeners,
logger,
}
}
@@ -91,7 +94,7 @@ impl Application {
}
}
impl crate::cloud_provider::service::Application for Application {
impl<'a> crate::cloud_provider::service::Application for Application<'a> {
fn image(&self) -> &Image {
&self.image
}
@@ -101,7 +104,7 @@ impl crate::cloud_provider::service::Application for Application {
}
}
impl Helm for Application {
impl<'a> Helm for Application<'a> {
fn helm_selector(&self) -> Option<String> {
self.selector()
}
@@ -123,15 +126,15 @@ impl Helm for Application {
}
}
impl StatelessService for Application {}
impl<'a> StatelessService for Application<'a> {}
impl ToTransmitter for Application {
impl<'a> ToTransmitter for Application<'a> {
fn to_transmitter(&self) -> Transmitter {
Transmitter::Application(self.id().to_string(), self.name().to_string())
}
}
impl Service for Application {
impl<'a> Service for Application<'a> {
fn context(&self) -> &Context {
&self.context
}
@@ -196,6 +199,7 @@ impl Service for Application {
}
fn tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration));
let kubernetes = target.kubernetes;
let environment = target.environment;
let mut context = default_tera_context(self, kubernetes, environment);
@@ -210,9 +214,15 @@ impl Service for Application {
),
None => {
let image_name_with_tag = self.image().name_with_tag();
warn!(
"there is no registry url, use image name with tag with the default container registry: {}",
image_name_with_tag.as_str()
self.logger().log(
LogLevel::Warning,
EngineEvent::Warning(
event_details.clone(),
EventMessage::new_from_safe(format!(
"there is no registry url, use image name with tag with the default container registry: {}",
image_name_with_tag.as_str()
)),
),
);
context.insert("image_name_with_tag", image_name_with_tag.as_str());
}
@@ -246,14 +256,16 @@ impl Service for Application {
&self.id,
self.total_cpus(),
self.cpu_burst(),
event_details.clone(),
self.logger(),
) {
Ok(l) => l,
Err(e) => {
return Err(EngineError::new(
Internal,
EngineErrorScope::Application(self.id().to_string(), self.name().to_string()),
self.context.execution_id(),
Some(e.to_string()),
return Err(EngineError::new_k8s_validate_required_cpu_and_burstable_error(
event_details.clone(),
self.total_cpus(),
self.cpu_burst(),
e,
));
}
};
@@ -310,23 +322,26 @@ impl Service for Application {
Some(format!("appId={}", self.id))
}
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::Application(self.id().to_string(), self.name().to_string())
fn logger(&self) -> &dyn Logger {
self.logger
}
}
impl Create for Application {
impl<'a> Create for Application<'a> {
#[named]
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || {
deploy_user_stateless_service(target, self)
deploy_user_stateless_service(target, self, event_details)
})
}
@@ -336,11 +351,14 @@ impl Create for Application {
#[named]
fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || {
@@ -349,14 +367,17 @@ impl Create for Application {
}
}
impl Pause for Application {
impl<'a> Pause for Application<'a> {
#[named]
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || {
@@ -375,18 +396,21 @@ impl Pause for Application {
#[named]
fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Delete for Application {
impl<'a> Delete for Application<'a> {
#[named]
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
@@ -395,10 +419,12 @@ impl Delete for Application {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || {
delete_stateless_service(target, self, false, event_details.clone())
delete_stateless_service(target, self, false, event_details)
})
}
@@ -414,15 +440,17 @@ impl Delete for Application {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || {
delete_stateless_service(target, self, true, event_details.clone())
delete_stateless_service(target, self, true, event_details)
})
}
}
impl Listen for Application {
impl<'a> Listen for Application<'a> {
fn listeners(&self) -> &Listeners {
&self.listeners
}

View File

@@ -3,19 +3,20 @@ use tera::Context as TeraContext;
use crate::cloud_provider::service::{
check_service_version, default_tera_context, delete_stateful_service, deploy_stateful_service, get_tfstate_name,
get_tfstate_suffix, scale_down_database, send_progress_on_long_task, Action, Create, Database, DatabaseOptions,
DatabaseType, Delete, Helm, Pause, Service, ServiceType, StatefulService, Terraform,
DatabaseType, Delete, Helm, Pause, Service, ServiceType, ServiceVersionCheckResult, StatefulService, Terraform,
};
use crate::cloud_provider::utilities::{get_self_hosted_mongodb_version, print_action, sanitize_name};
use crate::cloud_provider::DeploymentTarget;
use crate::cmd::helm::Timeout;
use crate::cmd::kubectl;
use crate::errors::EngineError;
use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter};
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter};
use crate::logger::Logger;
use crate::models::DatabaseMode::MANAGED;
use crate::models::{Context, Listen, Listener, Listeners};
use ::function_name::named;
pub struct MongoDB {
pub struct MongoDB<'a> {
context: Context,
id: String,
action: Action,
@@ -28,9 +29,10 @@ pub struct MongoDB {
database_instance_type: String,
options: DatabaseOptions,
listeners: Listeners,
logger: &'a dyn Logger,
}
impl MongoDB {
impl<'a> MongoDB<'a> {
pub fn new(
context: Context,
id: &str,
@@ -44,6 +46,7 @@ impl MongoDB {
database_instance_type: &str,
options: DatabaseOptions,
listeners: Listeners,
logger: &'a dyn Logger,
) -> Self {
MongoDB {
context,
@@ -58,11 +61,12 @@ impl MongoDB {
database_instance_type: database_instance_type.to_string(),
options,
listeners,
logger,
}
}
fn matching_correct_version(&self) -> Result<String, EngineError> {
check_service_version(get_self_hosted_mongodb_version(self.version()), self)
fn matching_correct_version(&self, event_details: EventDetails) -> Result<ServiceVersionCheckResult, EngineError> {
check_service_version(get_self_hosted_mongodb_version(self.version()), self, event_details)
}
fn cloud_provider_name(&self) -> &str {
@@ -74,13 +78,13 @@ impl MongoDB {
}
}
impl StatefulService for MongoDB {
impl<'a> StatefulService for MongoDB<'a> {
fn is_managed_service(&self) -> bool {
self.options.mode == MANAGED
}
}
impl ToTransmitter for MongoDB {
impl<'a> ToTransmitter for MongoDB<'a> {
fn to_transmitter(&self) -> Transmitter {
Transmitter::Database(
self.id().to_string(),
@@ -90,7 +94,7 @@ impl ToTransmitter for MongoDB {
}
}
impl Service for MongoDB {
impl<'a> Service for MongoDB<'a> {
fn context(&self) -> &Context {
&self.context
}
@@ -152,6 +156,7 @@ impl Service for MongoDB {
}
fn tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration));
let kubernetes = target.kubernetes;
let environment = target.environment;
@@ -170,7 +175,7 @@ impl Service for MongoDB {
context.insert("namespace", environment.namespace());
let version = self.matching_correct_version()?;
let version = self.matching_correct_version(event_details.clone())?.matched_version();
context.insert("version", &version);
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
@@ -211,14 +216,18 @@ impl Service for MongoDB {
Ok(context)
}
fn logger(&self) -> &dyn Logger {
self.logger
}
fn selector(&self) -> Option<String> {
Some(format!("app={}", self.sanitized_name()))
}
}
impl Database for MongoDB {}
impl<'a> Database for MongoDB<'a> {}
impl Helm for MongoDB {
impl<'a> Helm for MongoDB<'a> {
fn helm_selector(&self) -> Option<String> {
self.selector()
}
@@ -240,7 +249,7 @@ impl Helm for MongoDB {
}
}
impl Terraform for MongoDB {
impl<'a> Terraform for MongoDB<'a> {
fn terraform_common_resource_dir_path(&self) -> String {
format!("{}/scaleway/services/common", self.context.lib_root_dir())
}
@@ -250,7 +259,7 @@ impl Terraform for MongoDB {
}
}
impl Create for MongoDB {
impl<'a> Create for MongoDB<'a> {
#[named]
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
@@ -259,10 +268,12 @@ impl Create for MongoDB {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || {
deploy_stateful_service(target, self, event_details.clone())
deploy_stateful_service(target, self, event_details, self.logger())
})
}
@@ -272,24 +283,30 @@ impl Create for MongoDB {
#[named]
fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Pause for MongoDB {
impl<'a> Pause for MongoDB<'a> {
#[named]
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || {
@@ -303,18 +320,21 @@ impl Pause for MongoDB {
#[named]
fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Delete for MongoDB {
impl<'a> Delete for MongoDB<'a> {
#[named]
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
@@ -323,10 +343,12 @@ impl Delete for MongoDB {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || {
delete_stateful_service(target, self, event_details.clone())
delete_stateful_service(target, self, event_details, self.logger())
})
}
@@ -336,17 +358,20 @@ impl Delete for MongoDB {
#[named]
fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Listen for MongoDB {
impl<'a> Listen for MongoDB<'a> {
fn listeners(&self) -> &Listeners {
&self.listeners
}

View File

@@ -3,7 +3,7 @@ use tera::Context as TeraContext;
use crate::cloud_provider::service::{
check_service_version, default_tera_context, delete_stateful_service, deploy_stateful_service, get_tfstate_name,
get_tfstate_suffix, scale_down_database, send_progress_on_long_task, Action, Create, Database, DatabaseOptions,
DatabaseType, Delete, Helm, Pause, Service, ServiceType, StatefulService, Terraform,
DatabaseType, Delete, Helm, Pause, Service, ServiceType, ServiceVersionCheckResult, StatefulService, Terraform,
};
use crate::cloud_provider::utilities::{
get_self_hosted_mysql_version, get_supported_version_to_use, print_action, sanitize_name, VersionsNumber,
@@ -11,15 +11,16 @@ use crate::cloud_provider::utilities::{
use crate::cloud_provider::DeploymentTarget;
use crate::cmd::helm::Timeout;
use crate::cmd::kubectl;
use crate::error::{EngineError, EngineErrorCause, EngineErrorScope, StringError};
use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter};
use crate::errors::{CommandError, EngineError};
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter};
use crate::logger::Logger;
use crate::models::DatabaseMode::MANAGED;
use crate::models::{Context, Listen, Listener, Listeners};
use ::function_name::named;
use std::collections::HashMap;
use std::str::FromStr;
pub struct MySQL {
pub struct MySQL<'a> {
context: Context,
id: String,
action: Action,
@@ -32,9 +33,10 @@ pub struct MySQL {
database_instance_type: String,
options: DatabaseOptions,
listeners: Listeners,
logger: &'a dyn Logger,
}
impl MySQL {
impl<'a> MySQL<'a> {
pub fn new(
context: Context,
id: &str,
@@ -48,6 +50,7 @@ impl MySQL {
database_instance_type: &str,
options: DatabaseOptions,
listeners: Listeners,
logger: &dyn Logger,
) -> Self {
Self {
context,
@@ -62,21 +65,23 @@ impl MySQL {
database_instance_type: database_instance_type.to_string(),
options,
listeners,
logger,
}
}
fn matching_correct_version(&self, is_managed_services: bool) -> Result<VersionsNumber, EngineError> {
let version = check_service_version(Self::pick_mysql_version(self.version(), is_managed_services), self)?;
match VersionsNumber::from_str(version.as_str()) {
Ok(res) => Ok(res),
Err(e) => Err(self.engine_error(
EngineErrorCause::Internal,
format!("cannot parse database version, err: {}", e),
)),
}
fn matching_correct_version(
&self,
is_managed_services: bool,
event_details: EventDetails,
) -> Result<ServiceVersionCheckResult, EngineError> {
check_service_version(
Self::pick_mysql_version(self.version(), is_managed_services),
self,
event_details,
)
}
fn pick_mysql_version(requested_version: String, is_managed_service: bool) -> Result<String, StringError> {
fn pick_mysql_version(requested_version: String, is_managed_service: bool) -> Result<String, CommandError> {
if is_managed_service {
Self::pick_managed_mysql_version(requested_version)
} else {
@@ -84,7 +89,7 @@ impl MySQL {
}
}
fn pick_managed_mysql_version(requested_version: String) -> Result<String, StringError> {
fn pick_managed_mysql_version(requested_version: String) -> Result<String, CommandError> {
// Scaleway supported MySQL versions
// https://api.scaleway.com/rdb/v1/regions/fr-par/database-engines
let mut supported_mysql_versions = HashMap::new();
@@ -105,13 +110,13 @@ impl MySQL {
}
}
impl StatefulService for MySQL {
impl<'a> StatefulService for MySQL<'a> {
fn is_managed_service(&self) -> bool {
self.options.mode == MANAGED
}
}
impl ToTransmitter for MySQL {
impl<'a> ToTransmitter for MySQL<'a> {
fn to_transmitter(&self) -> Transmitter {
Transmitter::Database(
self.id().to_string(),
@@ -121,7 +126,7 @@ impl ToTransmitter for MySQL {
}
}
impl Service for MySQL {
impl<'a> Service for MySQL<'a> {
fn context(&self) -> &Context {
&self.context
}
@@ -183,18 +188,14 @@ impl Service for MySQL {
}
fn tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration));
let kubernetes = target.kubernetes;
let environment = target.environment;
let mut context = default_tera_context(self, kubernetes, environment);
// we need the kubernetes config file to store tfstates file in kube secrets
let kube_config_file_path = match kubernetes.get_kubeconfig_file_path() {
Ok(path) => path,
Err(e) => {
return Err(e.to_legacy_engine_error());
}
};
let kube_config_file_path = kubernetes.get_kubeconfig_file_path()?;
context.insert("kubeconfig_path", &kube_config_file_path);
kubectl::kubectl_exec_create_namespace_without_labels(
@@ -205,7 +206,9 @@ impl Service for MySQL {
context.insert("namespace", environment.namespace());
let version = &self.matching_correct_version(self.is_managed_service())?;
let version = &self
.matching_correct_version(self.is_managed_service(), event_details.clone())?
.matched_version();
context.insert("version_major", &version.to_major_version_string());
context.insert("version", &version.to_string()); // Scaleway needs to have major version only
@@ -256,18 +259,14 @@ impl Service for MySQL {
Some(format!("app={}", self.sanitized_name()))
}
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::Database(
self.id().to_string(),
self.service_type().name().to_string(),
self.name().to_string(),
)
fn logger(&self) -> &dyn Logger {
self.logger
}
}
impl Database for MySQL {}
impl<'a> Database for MySQL<'a> {}
impl Helm for MySQL {
impl<'a> Helm for MySQL<'a> {
fn helm_selector(&self) -> Option<String> {
self.selector()
}
@@ -289,7 +288,7 @@ impl Helm for MySQL {
}
}
impl Terraform for MySQL {
impl<'a> Terraform for MySQL<'a> {
fn terraform_common_resource_dir_path(&self) -> String {
format!("{}/scaleway/services/common", self.context.lib_root_dir())
}
@@ -299,7 +298,7 @@ impl Terraform for MySQL {
}
}
impl Create for MySQL {
impl<'a> Create for MySQL<'a> {
#[named]
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
@@ -308,10 +307,12 @@ impl Create for MySQL {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || {
deploy_stateful_service(target, self, event_details.clone())
deploy_stateful_service(target, self, event_details, self.logger())
})
}
@@ -321,25 +322,31 @@ impl Create for MySQL {
#[named]
fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Pause for MySQL {
impl<'a> Pause for MySQL<'a> {
#[named]
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || {
@@ -353,18 +360,21 @@ impl Pause for MySQL {
#[named]
fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Delete for MySQL {
impl<'a> Delete for MySQL<'a> {
#[named]
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
@@ -373,10 +383,12 @@ impl Delete for MySQL {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || {
delete_stateful_service(target, self, event_details.clone())
delete_stateful_service(target, self, event_details, self.logger())
})
}
@@ -386,17 +398,20 @@ impl Delete for MySQL {
#[named]
fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Listen for MySQL {
impl<'a> Listen for MySQL<'a> {
fn listeners(&self) -> &Listeners {
&self.listeners
}

View File

@@ -3,7 +3,7 @@ use tera::Context as TeraContext;
use crate::cloud_provider::service::{
check_service_version, default_tera_context, delete_stateful_service, deploy_stateful_service, get_tfstate_name,
get_tfstate_suffix, scale_down_database, send_progress_on_long_task, Action, Create, Database, DatabaseOptions,
DatabaseType, Delete, Helm, Pause, Service, ServiceType, StatefulService, Terraform,
DatabaseType, Delete, Helm, Pause, Service, ServiceType, ServiceVersionCheckResult, StatefulService, Terraform,
};
use crate::cloud_provider::utilities::{
get_self_hosted_postgres_version, get_supported_version_to_use, print_action, sanitize_name, VersionsNumber,
@@ -11,15 +11,16 @@ use crate::cloud_provider::utilities::{
use crate::cloud_provider::DeploymentTarget;
use crate::cmd::helm::Timeout;
use crate::cmd::kubectl;
use crate::error::{EngineError, EngineErrorCause, EngineErrorScope, StringError};
use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter};
use crate::errors::{CommandError, EngineError};
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter};
use crate::logger::Logger;
use crate::models::DatabaseMode::MANAGED;
use crate::models::{Context, Listen, Listener, Listeners};
use ::function_name::named;
use std::collections::HashMap;
use std::str::FromStr;
pub struct PostgreSQL {
pub struct PostgreSQL<'a> {
context: Context,
id: String,
action: Action,
@@ -32,9 +33,10 @@ pub struct PostgreSQL {
database_instance_type: String,
options: DatabaseOptions,
listeners: Listeners,
logger: &'a dyn Logger,
}
impl PostgreSQL {
impl<'a> PostgreSQL<'a> {
pub fn new(
context: Context,
id: &str,
@@ -48,6 +50,7 @@ impl PostgreSQL {
database_instance_type: &str,
options: DatabaseOptions,
listeners: Listeners,
logger: &'a dyn Logger,
) -> Self {
Self {
context,
@@ -62,21 +65,23 @@ impl PostgreSQL {
database_instance_type: database_instance_type.to_string(),
options,
listeners,
logger,
}
}
fn matching_correct_version(&self, is_managed_services: bool) -> Result<VersionsNumber, EngineError> {
let version = check_service_version(Self::pick_postgres_version(self.version(), is_managed_services), self)?;
match VersionsNumber::from_str(version.as_str()) {
Ok(res) => Ok(res),
Err(e) => Err(self.engine_error(
EngineErrorCause::Internal,
format!("cannot parse database version, err: {}", e),
)),
}
fn matching_correct_version(
&self,
is_managed_services: bool,
event_details: EventDetails,
) -> Result<ServiceVersionCheckResult, EngineError> {
check_service_version(
Self::pick_postgres_version(self.version(), is_managed_services),
self,
event_details,
)
}
fn pick_postgres_version(requested_version: String, is_managed_service: bool) -> Result<String, StringError> {
fn pick_postgres_version(requested_version: String, is_managed_service: bool) -> Result<String, CommandError> {
if is_managed_service {
Self::pick_managed_postgres_version(requested_version)
} else {
@@ -84,7 +89,7 @@ impl PostgreSQL {
}
}
fn pick_managed_postgres_version(requested_version: String) -> Result<String, StringError> {
fn pick_managed_postgres_version(requested_version: String) -> Result<String, CommandError> {
// Scaleway supported postgres versions
// https://api.scaleway.com/rdb/v1/regions/fr-par/database-engines
let mut supported_postgres_versions = HashMap::new();
@@ -114,13 +119,13 @@ impl PostgreSQL {
}
}
impl StatefulService for PostgreSQL {
impl<'a> StatefulService for PostgreSQL<'a> {
fn is_managed_service(&self) -> bool {
self.options.mode == MANAGED
}
}
impl ToTransmitter for PostgreSQL {
impl<'a> ToTransmitter for PostgreSQL<'a> {
fn to_transmitter(&self) -> Transmitter {
Transmitter::Database(
self.id().to_string(),
@@ -130,7 +135,7 @@ impl ToTransmitter for PostgreSQL {
}
}
impl Service for PostgreSQL {
impl<'a> Service for PostgreSQL<'a> {
fn context(&self) -> &Context {
&self.context
}
@@ -192,18 +197,14 @@ impl Service for PostgreSQL {
}
fn tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration));
let kubernetes = target.kubernetes;
let environment = target.environment;
let mut context = default_tera_context(self, kubernetes, environment);
// we need the kubernetes config file to store tfstates file in kube secrets
let kube_config_file_path = match kubernetes.get_kubeconfig_file_path() {
Ok(path) => path,
Err(e) => {
return Err(e.to_legacy_engine_error());
}
};
let kube_config_file_path = kubernetes.get_kubeconfig_file_path()?;
context.insert("kubeconfig_path", &kube_config_file_path);
kubectl::kubectl_exec_create_namespace_without_labels(
@@ -214,7 +215,9 @@ impl Service for PostgreSQL {
context.insert("namespace", environment.namespace());
let version = &self.matching_correct_version(self.is_managed_service())?;
let version = &self
.matching_correct_version(self.is_managed_service(), event_details.clone())?
.matched_version();
context.insert("version_major", &version.to_major_version_string());
context.insert("version", &version.to_string()); // Scaleway needs to have major version only
@@ -265,18 +268,14 @@ impl Service for PostgreSQL {
Some(format!("app={}", self.sanitized_name()))
}
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::Database(
self.id().to_string(),
self.service_type().name().to_string(),
self.name().to_string(),
)
fn logger(&self) -> &dyn Logger {
self.logger
}
}
impl Database for PostgreSQL {}
impl<'a> Database for PostgreSQL<'a> {}
impl Helm for PostgreSQL {
impl<'a> Helm for PostgreSQL<'a> {
fn helm_selector(&self) -> Option<String> {
self.selector()
}
@@ -298,7 +297,7 @@ impl Helm for PostgreSQL {
}
}
impl Terraform for PostgreSQL {
impl<'a> Terraform for PostgreSQL<'a> {
fn terraform_common_resource_dir_path(&self) -> String {
format!("{}/scaleway/services/common", self.context.lib_root_dir())
}
@@ -308,7 +307,7 @@ impl Terraform for PostgreSQL {
}
}
impl Create for PostgreSQL {
impl<'a> Create for PostgreSQL<'a> {
#[named]
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
@@ -317,10 +316,12 @@ impl Create for PostgreSQL {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || {
deploy_stateful_service(target, self, event_details.clone())
deploy_stateful_service(target, self, event_details, self.logger())
})
}
@@ -330,25 +331,31 @@ impl Create for PostgreSQL {
#[named]
fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Pause for PostgreSQL {
impl<'a> Pause for PostgreSQL<'a> {
#[named]
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || {
@@ -362,18 +369,21 @@ impl Pause for PostgreSQL {
#[named]
fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Delete for PostgreSQL {
impl<'a> Delete for PostgreSQL<'a> {
#[named]
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
@@ -382,10 +392,12 @@ impl Delete for PostgreSQL {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || {
delete_stateful_service(target, self, event_details.clone())
delete_stateful_service(target, self, event_details, self.logger())
})
}
@@ -395,17 +407,20 @@ impl Delete for PostgreSQL {
#[named]
fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Listen for PostgreSQL {
impl<'a> Listen for PostgreSQL<'a> {
fn listeners(&self) -> &Listeners {
&self.listeners
}

View File

@@ -3,19 +3,20 @@ use tera::Context as TeraContext;
use crate::cloud_provider::service::{
check_service_version, default_tera_context, delete_stateful_service, deploy_stateful_service, get_tfstate_name,
get_tfstate_suffix, scale_down_database, send_progress_on_long_task, Action, Create, Database, DatabaseOptions,
DatabaseType, Delete, Helm, Pause, Service, ServiceType, StatefulService, Terraform,
DatabaseType, Delete, Helm, Pause, Service, ServiceType, ServiceVersionCheckResult, StatefulService, Terraform,
};
use crate::cloud_provider::utilities::{get_self_hosted_redis_version, print_action, sanitize_name};
use crate::cloud_provider::DeploymentTarget;
use crate::cmd::helm::Timeout;
use crate::cmd::kubectl;
use crate::error::{EngineError, EngineErrorScope};
use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter};
use crate::errors::EngineError;
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter};
use crate::logger::Logger;
use crate::models::DatabaseMode::MANAGED;
use crate::models::{Context, Listen, Listener, Listeners};
use ::function_name::named;
pub struct Redis {
pub struct Redis<'a> {
context: Context,
id: String,
action: Action,
@@ -28,9 +29,10 @@ pub struct Redis {
database_instance_type: String,
options: DatabaseOptions,
listeners: Listeners,
logger: &'a dyn Logger,
}
impl Redis {
impl<'a> Redis<'a> {
pub fn new(
context: Context,
id: &str,
@@ -44,6 +46,7 @@ impl Redis {
database_instance_type: &str,
options: DatabaseOptions,
listeners: Listeners,
logger: &'a dyn Logger,
) -> Self {
Self {
context,
@@ -58,11 +61,12 @@ impl Redis {
database_instance_type: database_instance_type.to_string(),
options,
listeners,
logger,
}
}
fn matching_correct_version(&self) -> Result<String, EngineError> {
check_service_version(get_self_hosted_redis_version(self.version()), self)
fn matching_correct_version(&self, event_details: EventDetails) -> Result<ServiceVersionCheckResult, EngineError> {
check_service_version(get_self_hosted_redis_version(self.version()), self, event_details)
}
fn cloud_provider_name(&self) -> &str {
@@ -74,13 +78,13 @@ impl Redis {
}
}
impl StatefulService for Redis {
impl<'a> StatefulService for Redis<'a> {
fn is_managed_service(&self) -> bool {
self.options.mode == MANAGED
}
}
impl ToTransmitter for Redis {
impl<'a> ToTransmitter for Redis<'a> {
fn to_transmitter(&self) -> Transmitter {
Transmitter::Database(
self.id().to_string(),
@@ -90,7 +94,7 @@ impl ToTransmitter for Redis {
}
}
impl Service for Redis {
impl<'a> Service for Redis<'a> {
fn context(&self) -> &Context {
&self.context
}
@@ -152,18 +156,14 @@ impl Service for Redis {
}
fn tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration));
let kubernetes = target.kubernetes;
let environment = target.environment;
let mut context = default_tera_context(self, kubernetes, environment);
// we need the kubernetes config file to store tfstates file in kube secrets
let kube_config_file_path = match kubernetes.get_kubeconfig_file_path() {
Ok(path) => path,
Err(e) => {
return Err(e.to_legacy_engine_error());
}
};
let kube_config_file_path = kubernetes.get_kubeconfig_file_path()?;
context.insert("kubeconfig_path", &kube_config_file_path);
kubectl::kubectl_exec_create_namespace_without_labels(
@@ -172,7 +172,7 @@ impl Service for Redis {
kubernetes.cloud_provider().credentials_environment_variables(),
);
let version = self.matching_correct_version()?;
let version = self.matching_correct_version(event_details.clone())?.matched_version();
context.insert("namespace", environment.namespace());
context.insert("version", &version);
@@ -218,18 +218,14 @@ impl Service for Redis {
Some(format!("app={}", self.sanitized_name()))
}
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::Database(
self.id().to_string(),
self.service_type().name().to_string(),
self.name().to_string(),
)
fn logger(&self) -> &dyn Logger {
self.logger
}
}
impl Database for Redis {}
impl<'a> Database for Redis<'a> {}
impl Helm for Redis {
impl<'a> Helm for Redis<'a> {
fn helm_selector(&self) -> Option<String> {
self.selector()
}
@@ -251,7 +247,7 @@ impl Helm for Redis {
}
}
impl Terraform for Redis {
impl<'a> Terraform for Redis<'a> {
fn terraform_common_resource_dir_path(&self) -> String {
format!("{}/scaleway/services/common", self.context.lib_root_dir())
}
@@ -261,7 +257,7 @@ impl Terraform for Redis {
}
}
impl Create for Redis {
impl<'a> Create for Redis<'a> {
#[named]
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
@@ -270,10 +266,12 @@ impl Create for Redis {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || {
deploy_stateful_service(target, self, event_details.clone())
deploy_stateful_service(target, self, event_details, self.logger())
})
}
@@ -283,24 +281,30 @@ impl Create for Redis {
#[named]
fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Pause for Redis {
impl<'a> Pause for Redis<'a> {
#[named]
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Pause, || {
@@ -314,17 +318,20 @@ impl Pause for Redis {
#[named]
fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Delete for Redis {
impl<'a> Delete for Redis<'a> {
#[named]
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
@@ -333,10 +340,12 @@ impl Delete for Redis {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Delete, || {
delete_stateful_service(target, self, event_details.clone())
delete_stateful_service(target, self, event_details, self.logger())
})
}
@@ -346,17 +355,20 @@ impl Delete for Redis {
#[named]
fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Listen for Redis {
impl<'a> Listen for Redis<'a> {
fn listeners(&self) -> &Listeners {
&self.listeners
}

View File

@@ -3,7 +3,8 @@ use uuid::Uuid;
use crate::cloud_provider::{CloudProvider, EngineError, Kind, TerraformStateCredentials};
use crate::constants::{SCALEWAY_ACCESS_KEY, SCALEWAY_DEFAULT_PROJECT_ID, SCALEWAY_SECRET_KEY};
use crate::models::{Context, Listen, Listener, Listeners};
use crate::events::{EventDetails, Stage};
use crate::models::{Context, Listen, Listener, Listeners, QoveryIdentifier};
pub mod application;
pub mod databases;
@@ -119,6 +120,19 @@ impl CloudProvider for Scaleway {
fn as_any(&self) -> &dyn Any {
self
}
fn get_event_details(&self, stage: Stage) -> EventDetails {
let context = self.context();
EventDetails::new(
None,
QoveryIdentifier::from(context.organization_id().to_string()),
QoveryIdentifier::from(context.cluster_id().to_string()),
QoveryIdentifier::from(context.execution_id().to_string()),
None,
stage,
self.to_transmitter(),
)
}
}
impl Listen for Scaleway {

View File

@@ -8,13 +8,13 @@ use crate::cloud_provider::service::{
use crate::cloud_provider::utilities::{check_cname_for, print_action, sanitize_name};
use crate::cloud_provider::DeploymentTarget;
use crate::cmd::helm::Timeout;
use crate::error::{EngineError, EngineErrorCause, EngineErrorScope};
use crate::errors::EngineError as NewEngineError;
use crate::events::{EnvironmentStep, Stage, ToTransmitter, Transmitter};
use crate::errors::EngineError;
use crate::events::{EngineEvent, EnvironmentStep, EventMessage, Stage, ToTransmitter, Transmitter};
use crate::logger::{LogLevel, Logger};
use crate::models::{Context, Listen, Listener, Listeners};
use ::function_name::named;
pub struct Router {
pub struct Router<'a> {
context: Context,
id: String,
action: Action,
@@ -24,9 +24,10 @@ pub struct Router {
sticky_sessions_enabled: bool,
routes: Vec<Route>,
listeners: Listeners,
logger: &'a dyn Logger,
}
impl Router {
impl<'a> Router<'a> {
pub fn new(
context: Context,
id: &str,
@@ -37,7 +38,8 @@ impl Router {
routes: Vec<Route>,
sticky_sessions_enabled: bool,
listeners: Listeners,
) -> Router {
logger: &'a dyn Logger,
) -> Self {
Router {
context,
id: id.to_string(),
@@ -48,6 +50,7 @@ impl Router {
sticky_sessions_enabled,
routes,
listeners,
logger,
}
}
@@ -60,7 +63,7 @@ impl Router {
}
}
impl Service for Router {
impl<'a> Service for Router<'a> {
fn context(&self) -> &Context {
&self.context
}
@@ -122,6 +125,7 @@ impl Service for Router {
}
fn tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::LoadConfiguration));
let kubernetes = target.kubernetes;
let environment = target.environment;
@@ -193,12 +197,12 @@ impl Service for Router {
Some(format!("routerId={}", self.id))
}
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::Router(self.id().to_string(), self.name().to_string())
fn logger(&self) -> &dyn Logger {
self.logger
}
}
impl crate::cloud_provider::service::Router for Router {
impl<'a> crate::cloud_provider::service::Router for Router<'a> {
fn domains(&self) -> Vec<&str> {
let mut _domains = vec![self.default_domain.as_str()];
@@ -214,7 +218,7 @@ impl crate::cloud_provider::service::Router for Router {
}
}
impl Helm for Router {
impl<'a> Helm for Router<'a> {
fn helm_selector(&self) -> Option<String> {
self.selector()
}
@@ -236,7 +240,7 @@ impl Helm for Router {
}
}
impl Listen for Router {
impl<'a> Listen for Router<'a> {
fn listeners(&self) -> &Listeners {
&self.listeners
}
@@ -246,34 +250,33 @@ impl Listen for Router {
}
}
impl StatelessService for Router {}
impl<'a> StatelessService for Router<'a> {}
impl ToTransmitter for Router {
impl<'a> ToTransmitter for Router<'a> {
fn to_transmitter(&self) -> Transmitter {
Transmitter::Router(self.id().to_string(), self.name().to_string())
}
}
impl Create for Router {
impl<'a> Create for Router<'a> {
#[named]
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
let kubernetes = target.kubernetes;
let environment = target.environment;
let workspace_dir = self.workspace_directory();
let helm_release_name = self.helm_release_name();
let kubernetes_config_file_path = match kubernetes.get_kubeconfig_file_path() {
Ok(p) => p,
Err(e) => return Err(e.to_legacy_engine_error()),
};
let kubernetes_config_file_path = kubernetes.get_kubeconfig_file_path()?;
// respect order - getting the context here and not before is mandatory
// the nginx-ingress must be available to get the external dns target if necessary
@@ -283,13 +286,12 @@ impl Create for Router {
if let Err(e) =
crate::template::generate_and_copy_all_files_into_dir(from_dir.as_str(), workspace_dir.as_str(), context)
{
return Err(NewEngineError::new_cannot_copy_files_from_one_directory_to_another(
return Err(EngineError::new_cannot_copy_files_from_one_directory_to_another(
event_details.clone(),
from_dir.to_string(),
workspace_dir.to_string(),
e,
)
.to_legacy_engine_error());
));
}
// do exec helm upgrade and return the last deployment status
@@ -303,18 +305,18 @@ impl Create for Router {
kubernetes.cloud_provider().credentials_environment_variables(),
self.service_type(),
)
.map_err(|e| {
NewEngineError::new_helm_charts_upgrade_error(event_details.clone(), e).to_legacy_engine_error()
})?;
.map_err(|e| EngineError::new_helm_charts_upgrade_error(event_details.clone(), e).to_legacy_engine_error())?;
if helm_history_row.is_none() || !helm_history_row.unwrap().is_successfully_deployed() {
return Err(self.engine_error(EngineErrorCause::Internal, "Router has failed to be deployed".into()));
return Err(EngineError::new_router_failed_to_deploy(event_details.clone()));
}
Ok(())
}
fn on_create_check(&self) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
// check non custom domains
self.check_domains()?;
@@ -330,9 +332,19 @@ impl Create for Router {
continue
}
Ok(err) | Err(err) => {
warn!(
"Invalid CNAME for {}. Might not be an issue if user is using a CDN: {}",
domain_to_check.domain, err
// TODO(benjaminch): Handle better this one via a proper error eventually
self.logger().log(
LogLevel::Warning,
EngineEvent::Warning(
event_details.clone(),
EventMessage::new(
format!(
"Invalid CNAME for {}. Might not be an issue if user is using a CDN.",
domain_to_check.domain,
),
Some(err.to_string()),
),
),
);
}
}
@@ -343,11 +355,14 @@ impl Create for Router {
#[named]
fn on_create_error(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
send_progress_on_long_task(self, crate::cloud_provider::service::Action::Create, || {
@@ -356,14 +371,17 @@ impl Create for Router {
}
}
impl Pause for Router {
impl<'a> Pause for Router<'a> {
#[named]
fn on_pause(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
@@ -374,17 +392,20 @@ impl Pause for Router {
#[named]
fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
print_action(
self.cloud_provider_name(),
self.struct_name(),
function_name!(),
self.name(),
event_details,
self.logger(),
);
Ok(())
}
}
impl Delete for Router {
impl<'a> Delete for Router<'a> {
#[named]
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
@@ -393,6 +414,8 @@ impl Delete for Router {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
delete_router(target, self, false, event_details)
}
@@ -409,6 +432,8 @@ impl Delete for Router {
self.struct_name(),
function_name!(),
self.name(),
event_details.clone(),
self.logger(),
);
delete_router(target, self, true, event_details)
}

View File

@@ -9,7 +9,7 @@ use tera::Context as TeraContext;
use crate::build_platform::Image;
use crate::cloud_provider::environment::Environment;
use crate::cloud_provider::kubernetes::Kubernetes;
use crate::cloud_provider::utilities::{check_domain_for, VersionsNumber};
use crate::cloud_provider::utilities::check_domain_for;
use crate::cloud_provider::DeploymentTarget;
use crate::cmd::helm::Timeout;
use crate::cmd::kubectl::ScalingKind::Statefulset;

View File

@@ -1,6 +1,5 @@
use std::collections::HashMap;
use crate::cloud_provider::models::CpuLimits;
use crate::error::{EngineError, StringError};
use crate::errors::CommandError;
use crate::events::{EngineEvent, EventDetails, EventMessage};
@@ -14,7 +13,6 @@ use retry::delay::Fixed;
use retry::OperationResult;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::num::ParseFloatError;
use std::str::FromStr;
use trust_dns_resolver::config::*;
use trust_dns_resolver::proto::rr::{RData, RecordType};

View File

@@ -1,7 +1,9 @@
use crate::cmd;
use crate::cmd::utilities::QoveryCommand;
use crate::container_registry::Kind;
use crate::error::{SimpleError, SimpleErrorKind};
use crate::errors::CommandError;
use crate::events::{EngineEvent, EventDetails, EventMessage};
use crate::logger::{LogLevel, Logger};
use chrono::Duration;
use retry::delay::Fibonacci;
use retry::Error::Operation;
@@ -38,7 +40,9 @@ pub fn docker_manifest_inspect(
image_name: String,
image_tag: String,
registry_url: String,
) -> Option<DockerImageManifest> {
event_details: EventDetails,
logger: &dyn Logger,
) -> Result<DockerImageManifest, CommandError> {
let image_with_tag = format!("{}:{}", image_name, image_tag);
let registry_provider = match container_registry_kind {
Kind::DockerHub => "DockerHub",
@@ -62,26 +66,44 @@ pub fn docker_manifest_inspect(
Ok(_) => {
let joined = raw_output.join("");
match serde_json::from_str(&joined) {
Ok(extracted_manifest) => Some(extracted_manifest),
Ok(extracted_manifest) => Ok(extracted_manifest),
Err(e) => {
error!(
"error while trying to deserialize manifest image manifest for image {} in {} ({}): {:?}",
image_with_tag, registry_provider, registry_url, e,
let error = CommandError::new(
e.to_string(),
Some(format!(
"Error while trying to deserialize manifest image manifest for image {} in {} ({}).",
image_with_tag, registry_provider, registry_url,
)),
);
None
logger.log(
LogLevel::Warning,
EngineEvent::Warning(event_details.clone(), EventMessage::from(error.clone())),
);
Err(error)
}
}
}
Err(e) => {
error!(
"error while trying to inspect image manifest for image {} in {} ({}), command `{}`: {:?}",
image_with_tag,
registry_provider,
registry_url,
cmd::utilities::command_to_string(binary, &args, &envs),
e,
let error = CommandError::new(
format!(
"Command `{}`: {:?}",
cmd::utilities::command_to_string(binary, &args, &envs),
e
),
Some(format!(
"Error while trying to inspect image manifest for image {} in {} ({}).",
image_with_tag, registry_provider, registry_url,
)),
);
None
logger.log(
LogLevel::Warning,
EngineEvent::Warning(event_details.clone(), EventMessage::from(error.clone())),
);
Err(error)
}
};
}
@@ -92,7 +114,9 @@ pub fn docker_login(
registry_login: String,
registry_pass: String,
registry_url: String,
) -> Result<(), SimpleError> {
event_details: EventDetails,
logger: &dyn Logger,
) -> Result<(), CommandError> {
let registry_provider = match container_registry_kind {
Kind::DockerHub => "DockerHub",
Kind::Ecr => "AWS ECR",
@@ -114,16 +138,24 @@ pub fn docker_login(
match cmd.exec() {
Ok(_) => Ok(()),
Err(e) => {
let error_message = format!(
"error while trying to login to registry {} {}, command `{}`: {:?}",
registry_provider,
registry_url,
cmd::utilities::command_to_string(binary, &args, &docker_envs),
e,
let err = CommandError::new(
format!(
"Command `{}`: {:?}",
cmd::utilities::command_to_string(binary, &args, &docker_envs),
e,
),
Some(format!(
"Error while trying to login to registry {} {}.",
registry_provider, registry_url,
)),
);
error!("{}", error_message);
Err(SimpleError::new(SimpleErrorKind::Other, Some(error_message)))
logger.log(
LogLevel::Warning,
EngineEvent::Warning(event_details.clone(), EventMessage::from(err.clone())),
);
Err(err)
}
}
}
@@ -134,7 +166,9 @@ pub fn docker_tag_and_push_image(
image_name: String,
image_tag: String,
dest: String,
) -> Result<(), SimpleError> {
event_details: EventDetails,
logger: &dyn Logger,
) -> Result<(), CommandError> {
let image_with_tag = format!("{}:{}", image_name, image_tag);
let registry_provider = match container_registry_kind {
Kind::DockerHub => "DockerHub",
@@ -143,19 +177,37 @@ pub fn docker_tag_and_push_image(
Kind::ScalewayCr => "Scaleway Registry",
};
let mut cmd = QoveryCommand::new("docker", &vec!["tag", &image_with_tag, dest.as_str()], &docker_envs);
let binary = "docker";
let args = vec!["tag", &image_with_tag, dest.as_str()];
let mut cmd = QoveryCommand::new(binary, &args, &docker_envs);
match retry::retry(Fibonacci::from_millis(3000).take(5), || match cmd.exec() {
Ok(_) => OperationResult::Ok(()),
Err(e) => {
info!("failed to tag image {}, retrying...", image_with_tag);
logger.log(
LogLevel::Warning,
EngineEvent::Warning(
event_details.clone(),
EventMessage::new(
format!(
"Command `{}`: {:?}",
cmd::utilities::command_to_string(binary, &args, &docker_envs),
e
),
Some(format!("Failed to tag image {}, retrying...", image_with_tag)),
),
),
);
OperationResult::Retry(e)
}
}) {
Err(Operation { error, .. }) => {
return Err(SimpleError::new(
SimpleErrorKind::Other,
Some(format!("failed to tag image {}: {:?}", image_with_tag, error)),
))
logger.log(
LogLevel::Warning,
EngineEvent::Warning(event_details.clone(), EventMessage::from(error.clone())),
);
Err(error)
}
_ => {}
}
@@ -169,24 +221,39 @@ pub fn docker_tag_and_push_image(
) {
Ok(_) => OperationResult::Ok(()),
Err(e) => {
warn!(
"failed to push image {} on {}, {:?} retrying...",
image_with_tag, registry_provider, e
logger.log(
LogLevel::Warning,
EngineEvent::Warning(
event_details.clone(),
EventMessage::new(
format!(
"Failed to push image {} on {}, retrying...",
image_with_tag, registry_provider
),
Some(format!("{:?}", e)),
),
),
);
OperationResult::Retry(e)
}
}
}) {
Err(Operation { error, .. }) => Err(SimpleError::new(SimpleErrorKind::Other, Some(error.to_string()))),
Err(e) => Err(SimpleError::new(
SimpleErrorKind::Other,
Err(Operation { error, .. }) => Err(CommandError::new(error.to_string(), None)),
Err(e) => Err(CommandError::new(
format!("{:?}", e),
Some(format!(
"unknown error while trying to push image {} to {}. {:?}",
image_with_tag, registry_provider, e
"Unknown error while trying to push image {} to {}.",
image_with_tag, registry_provider,
)),
)),
_ => {
info!("image {} has successfully been pushed", image_with_tag);
logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(format!("image {} has successfully been pushed", image_with_tag)),
),
);
Ok(())
}
}
@@ -196,7 +263,9 @@ pub fn docker_pull_image(
container_registry_kind: Kind,
docker_envs: Vec<(&str, &str)>,
dest: String,
) -> Result<(), SimpleError> {
event_details: EventDetails,
logger: &dyn Logger,
) -> Result<(), CommandError> {
let registry_provider = match container_registry_kind {
Kind::DockerHub => "DockerHub",
Kind::Ecr => "AWS ECR",
@@ -213,31 +282,45 @@ pub fn docker_pull_image(
) {
Ok(_) => OperationResult::Ok(()),
Err(e) => {
warn!(
"failed to pull image from {} registry {}, {:?} retrying...",
registry_provider,
dest.as_str(),
e,
logger.log(
LogLevel::Warning,
EngineEvent::Warning(
event_details.clone(),
EventMessage::new(
format!(
"failed to pull image from {} registry {}, retrying...",
registry_provider,
dest.as_str(),
),
Some(format!("{:?}", e)),
),
),
);
OperationResult::Retry(e)
}
}
}) {
Err(Operation { error, .. }) => Err(SimpleError::new(SimpleErrorKind::Other, Some(error.to_string()))),
Err(e) => Err(SimpleError::new(
SimpleErrorKind::Other,
Err(Operation { error, .. }) => Err(CommandError::new(error.to_string(), None)),
Err(e) => Err(CommandError::new(
format!("{:?}", e),
Some(format!(
"unknown error while trying to pull image {} from {} registry. {:?}",
"Unknown error while trying to pull image {} from {} registry.",
dest.as_str(),
registry_provider,
e,
)),
)),
_ => {
info!(
"image {} has successfully been pulled from {} registry",
dest.as_str(),
registry_provider,
logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(format!(
"Image {} has successfully been pulled from {} registry",
dest.as_str(),
registry_provider,
)),
),
);
Ok(())
}

View File

@@ -6,22 +6,25 @@ use crate::build_platform::Image;
use crate::cmd::utilities::QoveryCommand;
use crate::container_registry::docker::{docker_pull_image, docker_tag_and_push_image};
use crate::container_registry::{ContainerRegistry, EngineError, Kind, PullResult, PushResult};
use crate::error::EngineErrorCause;
use crate::errors::CommandError;
use crate::events::{EngineEvent, EventMessage};
use crate::logger::{LogLevel, Logger};
use crate::models::{
Context, Listen, Listener, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope,
};
pub struct DockerHub {
pub struct DockerHub<'a> {
context: Context,
id: String,
name: String,
login: String,
password: String,
listeners: Listeners,
logger: &'a dyn Logger,
}
impl DockerHub {
pub fn new(context: Context, id: &str, name: &str, login: &str, password: &str) -> Self {
impl<'a> DockerHub<'a> {
pub fn new(context: Context, id: &str, name: &str, login: &str, password: &str, logger: &'a dyn Logger) -> Self {
DockerHub {
context,
id: id.to_string(),
@@ -29,10 +32,13 @@ impl DockerHub {
login: login.to_string(),
password: password.to_string(),
listeners: vec![],
logger,
}
}
pub fn exec_docker_login(&self) -> Result<(), EngineError> {
let event_details = self.get_event_details();
let envs = match self.context.docker_tcp_socket() {
Some(tcp_socket) => vec![("DOCKER_HOST", tcp_socket.as_str())],
None => vec![],
@@ -46,27 +52,25 @@ impl DockerHub {
match cmd.exec() {
Ok(_) => Ok(()),
Err(_) => Err(self.engine_error(
EngineErrorCause::User(
"Your DockerHub account seems to be no longer valid (bad Credentials). \
Please contact your Organization administrator to fix or change the Credentials.",
),
format!("failed to login to DockerHub {}", self.name_with_id()),
Err(_) => Err(EngineError::new_client_invalid_cloud_provider_credentials(
event_details,
)),
}
}
fn pull_image(&self, dest: String, image: &Image) -> Result<PullResult, EngineError> {
match docker_pull_image(self.kind(), vec![], dest.clone()) {
let event_details = self.get_event_details();
match docker_pull_image(self.kind(), vec![], dest.clone(), event_details.clone(), self.logger) {
Ok(_) => {
let mut image = image.clone();
image.registry_url = Some(dest);
Ok(PullResult::Some(image))
}
Err(e) => Err(self.engine_error(
EngineErrorCause::Internal,
e.message
.unwrap_or_else(|| "unknown error occurring during docker pull".to_string()),
Err(e) => Err(EngineError::new_docker_pull_image_error(
event_details,
image.name.to_string(),
dest.to_string(),
e,
)),
}
}
@@ -90,15 +94,6 @@ impl ContainerRegistry for DockerHub {
}
fn is_valid(&self) -> Result<(), EngineError> {
// check the version of docker and print it as info
let mut output_from_cmd = String::new();
let mut cmd = QoveryCommand::new("docker", &vec!["--version"], &vec![]);
let _ = cmd.exec_with_output(
|r_out| output_from_cmd.push_str(&r_out),
|r_err| error!("Error executing docker command {}", r_err),
);
info!("Using Docker: {}", output_from_cmd);
Ok(())
}
@@ -119,6 +114,7 @@ impl ContainerRegistry for DockerHub {
}
fn does_image_exists(&self, image: &Image) -> bool {
let event_details = self.get_event_details();
use reqwest::blocking::Client;
let client = Client::new();
let path = format!(
@@ -133,13 +129,27 @@ impl ContainerRegistry for DockerHub {
match res {
Ok(out) => matches!(out.status(), StatusCode::OK),
Err(e) => {
error!("While trying to retrieve if DockerHub repository exist {:?}", e);
self.logger.log(
LogLevel::Error,
EngineEvent::Error(
EngineError::new_container_registry_repository_doesnt_exist(
event_details.clone(),
image.name.to_string(),
Some(CommandError::new(
e.to_string(),
Some("Error while trying to retrieve if DockerHub repository exist.".to_string()),
)),
),
None,
),
);
false
}
}
}
fn pull(&self, image: &Image) -> Result<PullResult, EngineError> {
let event_details = self.get_event_details();
let listeners_helper = ListenersHelper::new(&self.listeners);
if !self.does_image_exists(image) {
@@ -148,7 +158,14 @@ impl ContainerRegistry for DockerHub {
image,
self.name()
);
info!("{}", info_message.as_str());
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(info_message.to_string()),
),
);
listeners_helper.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {
@@ -163,7 +180,14 @@ impl ContainerRegistry for DockerHub {
}
let info_message = format!("pull image {:?} from DockerHub {} repository", image, self.name());
info!("{}", info_message.as_str());
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(info_message.to_string()),
),
);
listeners_helper.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {
@@ -183,6 +207,8 @@ impl ContainerRegistry for DockerHub {
}
fn push(&self, image: &Image, force_push: bool) -> Result<PushResult, EngineError> {
let event_details = self.get_event_details();
let _ = self.exec_docker_login()?;
let dest = format!("{}/{}", self.login.as_str(), image.name_with_tag().as_str());
@@ -196,7 +222,13 @@ impl ContainerRegistry for DockerHub {
self.name()
);
info!("{}", info_message.as_str());
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(info_message.to_string()),
),
);
listeners_helper.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {
@@ -219,6 +251,14 @@ impl ContainerRegistry for DockerHub {
self.name()
);
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(info_message.to_string()),
),
);
listeners_helper.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {
id: image.application_id.clone(),
@@ -228,16 +268,25 @@ impl ContainerRegistry for DockerHub {
self.context.execution_id(),
));
match docker_tag_and_push_image(self.kind(), vec![], image.name.clone(), image.tag.clone(), dest.clone()) {
match docker_tag_and_push_image(
self.kind(),
vec![],
image.name.clone(),
image.tag.clone(),
dest.clone(),
event_details.clone(),
self.logger,
) {
Ok(_) => {
let mut image = image.clone();
image.registry_url = Some(dest);
Ok(PushResult { image })
}
Err(e) => Err(self.engine_error(
EngineErrorCause::Internal,
e.message
.unwrap_or_else(|| "unknown error occurring during docker push".to_string()),
Err(e) => Err(EngineError::new_docker_push_image_error(
event_details.clone(),
image.name.to_string(),
dest.to_string(),
e,
)),
}
}

View File

@@ -7,7 +7,9 @@ use crate::build_platform::Image;
use crate::cmd::utilities::QoveryCommand;
use crate::container_registry::docker::{docker_pull_image, docker_tag_and_push_image};
use crate::container_registry::{ContainerRegistry, EngineError, Kind, PullResult, PushResult};
use crate::error::{cast_simple_error_to_engine_error, EngineErrorCause, SimpleError, SimpleErrorKind};
use crate::errors::CommandError;
use crate::events::{EngineEvent, EventDetails, EventMessage};
use crate::logger::{LogLevel, Logger};
use crate::models::{
Context, Listen, Listener, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope,
};
@@ -22,40 +24,42 @@ const CR_CLUSTER_API_PATH: &str = "https://api.digitalocean.com/v2/kubernetes/re
// TODO : use --output json
// see https://www.digitalocean.com/community/tutorials/how-to-use-doctl-the-official-digitalocean-command-line-client
pub struct DOCR {
pub struct DOCR<'a> {
pub context: Context,
pub name: String,
pub api_key: String,
pub id: String,
pub listeners: Listeners,
logger: &'a dyn Logger,
}
impl DOCR {
pub fn new(context: Context, id: &str, name: &str, api_key: &str) -> Self {
impl<'a> DOCR<'a> {
pub fn new(context: Context, id: &str, name: &str, api_key: &str, logger: &dyn Logger) -> Self {
DOCR {
context,
name: name.into(),
api_key: api_key.into(),
id: id.into(),
listeners: vec![],
logger,
}
}
fn get_registry_name(&self, image: &Image) -> Result<String, EngineError> {
let event_details = self.get_event_details();
let registry_name = match image.registry_name.as_ref() {
// DOCR does not support upper cases
Some(registry_name) => registry_name.to_lowercase(),
None => cast_simple_error_to_engine_error(
self.engine_error_scope(),
self.context().execution_id(),
get_current_registry_name(self.api_key.as_str()),
)?,
None => get_current_registry_name(self.api_key.as_str(), event_details)?,
};
Ok(registry_name)
}
fn create_repository(&self, image: &Image) -> Result<(), EngineError> {
let event_details = self.get_event_details();
let registry_name = match image.registry_name.as_ref() {
// DOCR does not support upper cases
Some(registry_name) => registry_name.to_lowercase(),
@@ -83,46 +87,71 @@ impl DOCR {
StatusCode::OK => Ok(()),
StatusCode::CREATED => Ok(()),
status => {
warn!("status from DO registry API {}", status);
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"Bad status code : {} returned by the DO registry API for creating DO CR {}",
return Err(EngineError::new_container_registry_namespace_creation_error(
event_details.clone(),
self.name_with_id(),
registry_name.to_string(),
CommandError::new_from_safe_message(format!(
"Bad status code: `{}` returned by the DO registry API for creating DOCR `{}`.",
status,
registry_name.as_str(),
),
)),
));
}
},
Err(e) => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!("failed to create DOCR repository {} : {:?}", registry_name.as_str(), e,),
return Err(EngineError::new_container_registry_namespace_creation_error(
event_details.clone(),
self.name_with_id(),
registry_name.to_string(),
CommandError::new(
e.to_string(),
Some(format!(
"Failed to create DOCR repository `{}`.",
registry_name.as_str(),
)),
),
));
}
}
}
Err(e) => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!("Unable to initialize DO Registry {} : {:?}", registry_name.as_str(), e,),
return Err(EngineError::new_container_registry_namespace_creation_error(
event_details.clone(),
self.name_with_id(),
registry_name.to_string(),
CommandError::new(
e.to_string(),
Some(format!(
"Failed to create DOCR repository `{}`.",
registry_name.as_str(),
)),
),
));
}
}
}
fn push_image(&self, registry_name: String, dest: String, image: &Image) -> Result<PushResult, EngineError> {
let _ =
match docker_tag_and_push_image(self.kind(), vec![], image.name.clone(), image.tag.clone(), dest.clone()) {
Ok(_) => {}
Err(e) => {
return Err(self.engine_error(
EngineErrorCause::Internal,
e.message
.unwrap_or_else(|| "unknown error occurring during docker push".to_string()),
));
}
};
let event_details = self.get_event_details();
match docker_tag_and_push_image(
self.kind(),
vec![],
image.name.clone(),
image.tag.clone(),
dest.clone(),
event_details.clone(),
self.logger,
) {
Ok(_) => {}
Err(e) => Err(EngineError::new_docker_push_image_error(
event_details,
image.name.to_string(),
dest.to_string(),
e,
)),
}
let mut image = image.clone();
image.registry_name = Some(registry_name.clone());
@@ -134,15 +163,23 @@ impl DOCR {
match self.does_image_exists(&image) {
true => OperationResult::Ok(&image),
false => {
warn!("image is not yet available on Digital Ocean Registry, retrying in a few seconds...");
self.logger.log(
LogLevel::Warning,
EngineEvent::Warning(
self.get_event_details(),
EventMessage::new_from_safe(
"Image is not yet available on DOCR, retrying in a few seconds...".to_string(),
),
),
);
OperationResult::Retry(())
}
}
});
let image_not_reachable = Err(self.engine_error(
EngineErrorCause::Internal,
"image has been pushed on Digital Ocean Registry but is not yet available after 2min. Please try to redeploy in a few minutes".to_string(),
let image_not_reachable = Err(EngineError::new_container_registry_image_unreachable_after_push(
event_details.clone(),
image.name.to_string(),
));
match result {
Ok(_) => Ok(PushResult { image }),
@@ -161,6 +198,8 @@ impl DOCR {
}
pub fn delete_repository(&self) -> Result<(), EngineError> {
let event_details = self.get_event_details();
let headers = utilities::get_header_with_bearer(&self.api_key);
let res = reqwest::blocking::Client::new()
.delete(CR_API_PATH)
@@ -171,26 +210,32 @@ impl DOCR {
Ok(out) => match out.status() {
StatusCode::NO_CONTENT => Ok(()),
status => {
warn!("delete status from DO registry API {}", status);
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"Bad status code : {} returned by the DO registry API for deleting DOCR repository",
return Err(EngineError::new_container_registry_delete_repository_error(
event_details.clone(),
"default".to_string(), // DO has only one repository
Some(CommandError::new_from_safe_message(format!(
"Bad status code: `{}` returned by the DO registry API for deleting DOCR.",
status,
),
))),
));
}
},
Err(e) => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!("No response from the Digital Ocean API : {:?}", e),
return Err(EngineError::new_container_registry_delete_repository_error(
event_details.clone(),
"default".to_string(), // DO has only one repository
Some(CommandError::new(
e.to_string(),
Some("No response from the Digital Ocean API.".to_string()),
)),
));
}
}
}
pub fn exec_docr_login(&self) -> Result<(), EngineError> {
let event_details = self.get_event_details();
let mut cmd = QoveryCommand::new(
"doctl",
&vec!["registry", "login", self.name.as_str(), "-t", self.api_key.as_str()],
@@ -199,18 +244,16 @@ impl DOCR {
match cmd.exec() {
Ok(_) => Ok(()),
Err(_) => Err(self.engine_error(
EngineErrorCause::User(
"Your DOCR account seems to be no longer valid (bad Credentials). \
Please contact your Organization administrator to fix or change the Credentials.",
),
format!("failed to login to DOCR {}", self.name_with_id()),
Err(_) => Err(EngineError::new_client_invalid_cloud_provider_credentials(
event_details,
)),
}
}
fn pull_image(&self, registry_name: String, dest: String, image: &Image) -> Result<PullResult, EngineError> {
match docker_pull_image(self.kind(), vec![], dest.clone()) {
let event_details = self.get_event_details();
match docker_pull_image(self.kind(), vec![], dest.clone(), event_details.clone(), self.logger()) {
Ok(_) => {
let mut image = image.clone();
image.registry_name = Some(registry_name.clone());
@@ -219,10 +262,11 @@ impl DOCR {
image.registry_url = Some(dest);
Ok(PullResult::Some(image))
}
Err(e) => Err(self.engine_error(
EngineErrorCause::Internal,
e.message
.unwrap_or_else(|| "unknown error occurring during docker pull".to_string()),
Err(e) => Err(EngineError::new_docker_pull_image_error(
event_details,
image.name.to_string(),
dest.to_string(),
e,
)),
}
}
@@ -266,10 +310,12 @@ impl ContainerRegistry for DOCR {
}
fn does_image_exists(&self, image: &Image) -> bool {
let event_details = self.get_event_details();
let registry_name = match self.get_registry_name(image) {
Ok(registry_name) => registry_name,
Err(err) => {
warn!("{:?}", err);
self.logger.log(LogLevel::Error, EngineEvent::Error(err, None));
return false;
}
};
@@ -290,18 +336,38 @@ impl ContainerRegistry for DOCR {
Ok(output) => match output.status() {
StatusCode::OK => output.text(),
_ => {
error!(
"While tyring to get all tags for image: {}, maybe this image not exist !",
&image.name
self.logger.log(
LogLevel::Error,
EngineEvent::Error(
EngineError::new_container_registry_image_doesnt_exist(
event_details.clone(),
image.name.to_string(),
Some(CommandError::new_from_safe_message(format!(
"While tyring to get all tags for image: `{}`, maybe this image not exist !",
image.name.to_string()
))),
),
None,
),
);
return false;
}
},
Err(_) => {
error!(
"While trying to communicate with DigitalOcean API to retrieve all tags for image {}",
&image.name
self.logger.log(
LogLevel::Error,
EngineEvent::Error(
EngineError::new_container_registry_image_doesnt_exist(
event_details.clone(),
image.name.to_string(),
Some(CommandError::new_from_safe_message(format!(
"While trying to communicate with DigitalOcean API to retrieve all tags for image `{}`.",
image.name.to_string()
))),
),
None,
),
);
return false;
@@ -322,9 +388,22 @@ impl ContainerRegistry for DOCR {
false
}
Err(_) => {
error!(
"Unable to deserialize tags from DigitalOcean API for image {}",
&image.tag
self.logger.log(
LogLevel::Error,
EngineEvent::Error(
EngineError::new_container_registry_image_doesnt_exist(
event_details.clone(),
image.name.to_string(),
Some(CommandError::new(
out.to_string(),
Some(format!(
"Unable to deserialize tags from DigitalOcean API for image {}",
&image.tag.to_string(),
)),
)),
),
None,
),
);
false
@@ -332,9 +411,19 @@ impl ContainerRegistry for DOCR {
}
}
_ => {
error!(
"while retrieving tags for image {} Unable to get output from DigitalOcean API",
&image.name
self.logger.log(
LogLevel::Error,
EngineEvent::Error(
EngineError::new_container_registry_image_doesnt_exist(
event_details.clone(),
image.name.to_string(),
Some(CommandError::new_from_safe_message(format!(
"While retrieving tags for image `{}` Unable to get output from DigitalOcean API.",
image.name.to_string()
))),
),
None,
),
);
false
@@ -343,11 +432,19 @@ impl ContainerRegistry for DOCR {
}
fn pull(&self, image: &Image) -> Result<PullResult, EngineError> {
let event_details = self.get_event_details();
let listeners_helper = ListenersHelper::new(&self.listeners);
if !self.does_image_exists(image) {
let info_message = format!("image {:?} does not exist in DOCR {} repository", image, self.name());
info!("{}", info_message.as_str());
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(info_message.to_string()),
),
);
listeners_helper.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {
@@ -362,7 +459,14 @@ impl ContainerRegistry for DOCR {
}
let info_message = format!("pull image {:?} from DOCR {} repository", image, self.name());
info!("{}", info_message.as_str());
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(info_message.to_string()),
),
);
listeners_helper.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {
@@ -389,11 +493,27 @@ impl ContainerRegistry for DOCR {
// https://www.digitalocean.com/docs/images/container-registry/how-to/use-registry-docker-kubernetes/
fn push(&self, image: &Image, force_push: bool) -> Result<PushResult, EngineError> {
let event_details = self.get_event_details();
let registry_name = self.get_registry_name(image)?;
match self.create_repository(image) {
Ok(_) => info!("DOCR {} has been created", registry_name.as_str()),
Err(_) => warn!("DOCR {} already exists", registry_name.as_str()),
Ok(_) => self.logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(format!("DOCR {} has been created", registry_name.as_str())),
),
),
Err(e) => self.logger.log(
LogLevel::Error,
EngineEvent::Error(
e.clone(),
Some(EventMessage::new_from_safe(format!(
"DOCR {} already exists",
registry_name.as_str()
))),
),
),
};
let _ = self.exec_docr_login()?;
@@ -414,7 +534,13 @@ impl ContainerRegistry for DOCR {
registry_name.as_str()
);
info!("{}", info_message.as_str());
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(info_message.to_string()),
),
);
listeners_helper.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {
@@ -439,6 +565,14 @@ impl ContainerRegistry for DOCR {
image, registry_name
);
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(info_message.to_string()),
),
);
listeners_helper.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {
id: image.application_id.clone(),
@@ -466,7 +600,7 @@ impl Listen for DOCR {
}
}
pub fn subscribe_kube_cluster_to_container_registry(api_key: &str, cluster_uuid: &str) -> Result<(), SimpleError> {
pub fn subscribe_kube_cluster_to_container_registry(api_key: &str, cluster_uuid: &str) -> Result<(), CommandError> {
let headers = utilities::get_header_with_bearer(api_key);
let cluster_ids = DoApiSubscribeToKubeCluster {
cluster_uuids: vec![cluster_uuid.to_string()],
@@ -484,31 +618,25 @@ pub fn subscribe_kube_cluster_to_container_registry(api_key: &str, cluster_uuid:
match res {
Ok(output) => match output.status() {
StatusCode::NO_CONTENT => Ok(()),
status => {
warn!("status from DO registry API {}", status);
Err(SimpleError::new(SimpleErrorKind::Other, Some("Incorrect Status received from Digital Ocean when tyring to subscribe repository to cluster")))
}
status => Err(CommandError::new_from_safe_message(
"Incorrect Status received from Digital Ocean when tyring to subscribe repository to cluster"
.to_string(),
)),
},
Err(e) => {
error!("{:?}", e);
Err(SimpleError::new(
SimpleErrorKind::Other,
Some("Unable to call Digital Ocean when tyring to subscribe repository to cluster"),
))
}
Err(e) => Err(CommandError::new(
e.to_string(),
Some("Unable to call Digital Ocean when tyring to subscribe repository to cluster".to_string()),
)),
}
}
Err(e) => {
error!("{:?}", e);
Err(SimpleError::new(
SimpleErrorKind::Other,
Some("Unable to Serialize digital ocean cluster uuids"),
))
}
Err(e) => Err(CommandError::new(
e.to_string(),
Some("Unable to Serialize digital ocean cluster uuids".to_string()),
)),
};
}
pub fn get_current_registry_name(api_key: &str) -> Result<String, SimpleError> {
pub fn get_current_registry_name(api_key: &str, event_details: EventDetails) -> Result<String, EngineError> {
let headers = utilities::get_header_with_bearer(api_key);
let res = reqwest::blocking::Client::new()
.get(CR_API_PATH)
@@ -523,28 +651,41 @@ pub fn get_current_registry_name(api_key: &str) -> Result<String, SimpleError> {
match res_registry {
Ok(registry) => Ok(registry.registry.name),
Err(err) => Err(SimpleError::new(
SimpleErrorKind::Other,
Some(format!(
"An error occurred while deserializing JSON coming from Digital Ocean API: error: {:?}",
err
Err(err) => Err(EngineError::new_container_registry_repository_doesnt_exist(
event_details.clone(),
"default".to_string(), // DO has only one repository
Some(CommandError::new(
err.to_string(),
Some(
"An error occurred while deserializing JSON coming from Digital Ocean API.".to_string(),
),
)),
)),
}
}
status => {
warn!("status from Digital Ocean Registry API {}", status);
Err(SimpleError::new(
SimpleErrorKind::Other,
Some("Incorrect Status received from Digital Ocean when tyring to get container registry"),
Err(EngineError::new_container_registry_repository_doesnt_exist(
event_details.clone(),
"default".to_string(), // DO has only one repository
Some(CommandError::new(
format!("Status: {}", status),
Some(
"Incorrect Status received from Digital Ocean when tyring to get container registry."
.to_string(),
),
)),
))
}
},
Err(e) => {
error!("{:?}", e);
Err(SimpleError::new(
SimpleErrorKind::Other,
Some("Unable to call Digital Ocean when tyring to fetch the container registry name"),
Err(EngineError::new_container_registry_repository_doesnt_exist(
event_details.clone(),
"default".to_string(), // DO has only one repository
Some(CommandError::new(
e.to_string(),
Some("Unable to call Digital Ocean when tyring to fetch the container registry name.".to_string()),
)),
))
}
};

View File

@@ -12,7 +12,9 @@ use crate::build_platform::Image;
use crate::cmd::utilities::QoveryCommand;
use crate::container_registry::docker::{docker_pull_image, docker_tag_and_push_image};
use crate::container_registry::{ContainerRegistry, Kind, PullResult, PushResult};
use crate::error::{EngineError, EngineErrorCause};
use crate::errors::{CommandError, EngineError};
use crate::events::{EngineEvent, EventMessage};
use crate::logger::{LogLevel, Logger};
use crate::models::{
Context, Listen, Listener, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope,
};
@@ -22,7 +24,7 @@ use retry::Error::Operation;
use retry::OperationResult;
use serde_json::json;
pub struct ECR {
pub struct ECR<'a> {
context: Context,
id: String,
name: String,
@@ -30,9 +32,10 @@ pub struct ECR {
secret_access_key: String,
region: Region,
listeners: Listeners,
logger: &'a dyn Logger,
}
impl ECR {
impl<'a> ECR<'a> {
pub fn new(
context: Context,
id: &str,
@@ -40,6 +43,7 @@ impl ECR {
access_key_id: &str,
secret_access_key: &str,
region: &str,
logger: &'a dyn Logger,
) -> Self {
ECR {
context,
@@ -49,6 +53,7 @@ impl ECR {
secret_access_key: secret_access_key.to_string(),
region: Region::from_str(region).unwrap(),
listeners: vec![],
logger,
}
}
@@ -115,6 +120,7 @@ impl ECR {
fn push_image(&self, dest: String, image: &Image) -> Result<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
let event_details = self.get_event_details();
match docker_tag_and_push_image(
self.kind(),
@@ -122,16 +128,19 @@ impl ECR {
image.name.clone(),
image.tag.clone(),
dest.clone(),
event_details.clone(),
self.logger(),
) {
Ok(_) => {
let mut image = image.clone();
image.registry_url = Some(dest);
Ok(PushResult { image })
}
Err(e) => Err(self.engine_error(
EngineErrorCause::Internal,
e.message
.unwrap_or_else(|| "unknown error occurring during docker push".to_string()),
Err(e) => Err(EngineError::new_docker_push_image_error(
event_details,
image.name.to_string(),
dest.to_string(),
e,
)),
}
}
@@ -139,24 +148,40 @@ impl ECR {
fn pull_image(&self, dest: String, image: &Image) -> Result<PullResult, EngineError> {
// READ https://docs.aws.amazon.com/AmazonECR/latest/userguide/docker-pull-ecr-image.html
// docker pull aws_account_id.dkr.ecr.us-west-2.amazonaws.com/amazonlinux:latest
let event_details = self.get_event_details();
match docker_pull_image(self.kind(), self.docker_envs(), dest.clone()) {
match docker_pull_image(
self.kind(),
self.docker_envs(),
dest.clone(),
event_details.clone(),
self.logger(),
) {
Ok(_) => {
let mut image = image.clone();
image.registry_url = Some(dest);
Ok(PullResult::Some(image))
}
Err(e) => Err(self.engine_error(
EngineErrorCause::Internal,
e.message
.unwrap_or_else(|| "unknown error occurring during docker pull".to_string()),
Err(e) => Err(EngineError::new_docker_pull_image_error(
event_details,
image.name.to_string(),
dest.to_string(),
e,
)),
}
}
fn create_repository(&self, image: &Image) -> Result<Repository, EngineError> {
let event_details = self.get_event_details();
let repository_name = image.name.as_str();
info!("creating ECR repository {}", &repository_name);
self.logger().log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(format!("Creating ECR repository {}", &repository_name)),
),
);
let mut repo_creation_counter = 0;
let container_registry_request = DescribeRepositoriesRequest {
@@ -176,7 +201,13 @@ impl ECR {
.describe_repositories(container_registry_request.clone()),
) {
Ok(x) => {
debug!("created {:?} repository", x);
self.logger().log(
LogLevel::Debug,
EngineEvent::Debug(
event_details.clone(),
EventMessage::new_from_safe(format!("Created {:?} repository", x)),
),
);
OperationResult::Ok(())
}
Err(e) => {
@@ -184,40 +215,78 @@ impl ECR {
RusotoError::Service(s) => match s {
DescribeRepositoriesError::RepositoryNotFound(_) => {
if repo_creation_counter != 0 {
warn!(
"repository {} was not found, {}x retrying...",
&repository_name, &repo_creation_counter
self.logger().log(
LogLevel::Warning,
EngineEvent::Warning(
event_details.clone(),
EventMessage::new_from_safe(format!(
"Repository {} was not found, {}x retrying...",
&repository_name, &repo_creation_counter
)),
),
);
}
repo_creation_counter += 1;
}
_ => warn!("{:?}", s),
_ => self.logger().log(
LogLevel::Warning,
EngineEvent::Warning(
event_details.clone(),
EventMessage::new(
"Error while trying to create repository.".to_string(),
Some(format!("{:?}", s)),
),
),
),
},
_ => warn!("{:?}", e),
_ => self.logger().log(
LogLevel::Warning,
EngineEvent::Warning(
event_details.clone(),
EventMessage::new(
"Error while trying to create repository.".to_string(),
Some(format!("{:?}", e)),
),
),
),
}
// TODO: This behavior is weird, returning an ok message saying repository has been created in an error ...
let msg = match block_on(self.ecr_client().create_repository(crr.clone())) {
Ok(_) => format!("repository {} created", &repository_name),
Err(err) => format!(
"can't create ECR repository {} for {}. {:?}",
&repository_name,
self.name_with_id(),
err
),
Err(err) => format!("{:?}", err),
};
OperationResult::Retry(Err(self.engine_error(EngineErrorCause::Internal, msg)))
OperationResult::Retry(Err(EngineError::new_container_registry_namespace_creation_error(
event_details.clone(),
repository_name.to_string(),
self.name_with_id(),
CommandError::new(msg.to_string(), Some("Can't create ECR repository".to_string())),
)))
}
}
});
match repo_created {
Ok(_) => info!(
"repository {} created after {} attempt(s)",
&repository_name, repo_creation_counter
Ok(_) => self.logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(format!(
"repository {} created after {} attempt(s)",
&repository_name, repo_creation_counter,
)),
),
),
Err(Operation { error, .. }) => return error,
Err(retry::Error::Internal(e)) => return Err(self.engine_error(EngineErrorCause::Internal, e)),
Err(retry::Error::Internal(e)) => {
return Err(EngineError::new_container_registry_namespace_creation_error(
event_details.clone(),
repository_name.to_string(),
self.name_with_id(),
CommandError::new_from_safe_message(e),
))
}
};
// apply retention policy
@@ -250,32 +319,30 @@ impl ECR {
};
match block_on(self.ecr_client().put_lifecycle_policy(plp)) {
Err(err) => {
error!(
"can't set lifecycle policy to ECR repository {} for {}: {}",
image.name.as_str(),
self.name_with_id(),
err
);
Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"can't set lifecycle policy to ECR repository {} for {}",
image.name.as_str(),
self.name_with_id()
),
))
}
Err(err) => Err(
EngineError::new_container_registry_repository_set_lifecycle_policy_error(
event_details.clone(),
repository_name.to_string(),
CommandError::new_from_safe_message(err.to_string()),
),
),
_ => Ok(self.get_repository(image).expect("cannot get repository")),
}
}
fn get_or_create_repository(&self, image: &Image) -> Result<Repository, EngineError> {
let event_details = self.get_event_details();
// check if the repository already exists
let repository = self.get_repository(image);
if repository.is_some() {
info!("ECR repository {} already exists", image.name.as_str());
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(format!("ECR repository {} already exists", image.name.as_str())),
),
);
return Ok(repository.unwrap());
}
@@ -283,6 +350,7 @@ impl ECR {
}
fn get_credentials(&self) -> Result<ECRCredentials, EngineError> {
let event_details = self.get_event_details();
let r = block_on(
self.ecr_client()
.get_authorization_token(GetAuthorizationTokenRequest::default()),
@@ -306,22 +374,16 @@ impl ECR {
)
}
None => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"failed to retrieve credentials and endpoint URL from ECR {}",
self.name_with_id(),
),
return Err(EngineError::new_container_registry_get_credentials_error(
event_details.clone(),
self.name_with_id(),
));
}
},
_ => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"failed to retrieve credentials and endpoint URL from ECR {}",
self.name_with_id(),
),
return Err(EngineError::new_container_registry_get_credentials_error(
event_details.clone(),
self.name_with_id(),
));
}
};
@@ -330,6 +392,7 @@ impl ECR {
}
fn exec_docker_login(&self) -> Result<(), EngineError> {
let event_details = self.get_event_details();
let credentials = self.get_credentials()?;
let mut cmd = QoveryCommand::new(
@@ -346,12 +409,8 @@ impl ECR {
);
if let Err(_) = cmd.exec() {
return Err(self.engine_error(
EngineErrorCause::User(
"Your ECR account seems to be no longer valid (bad Credentials). \
Please contact your Organization administrator to fix or change the Credentials.",
),
format!("failed to login to ECR {}", self.name_with_id()),
return Err(EngineError::new_client_invalid_cloud_provider_credentials(
event_details.clone(),
));
};
@@ -382,30 +441,54 @@ impl ContainerRegistry for ECR {
match s {
Ok(_) => Ok(()),
Err(_) => Err(self.engine_error(
EngineErrorCause::User(
"Your ECR account seems to be no longer valid (bad Credentials). \
Please contact your Organization administrator to fix or change the Credentials.",
),
format!("bad ECR credentials for {}", self.name_with_id()),
Err(_) => Err(EngineError::new_client_invalid_cloud_provider_credentials(
self.get_event_details(),
)),
}
}
fn on_create(&self) -> Result<(), EngineError> {
info!("ECR.on_create() called");
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
self.get_event_details(),
EventMessage::new_from_safe("ECR.on_create() called".to_string()),
),
);
Ok(())
}
fn on_create_error(&self) -> Result<(), EngineError> {
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
self.get_event_details(),
EventMessage::new_from_safe("ECR.on_create_error() called".to_string()),
),
);
unimplemented!()
}
fn on_delete(&self) -> Result<(), EngineError> {
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
self.get_event_details(),
EventMessage::new_from_safe("ECR.on_delete() called".to_string()),
),
);
unimplemented!()
}
fn on_delete_error(&self) -> Result<(), EngineError> {
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
self.get_event_details(),
EventMessage::new_from_safe("ECR.on_delete_error() called".to_string()),
),
);
unimplemented!()
}
@@ -414,11 +497,19 @@ impl ContainerRegistry for ECR {
}
fn pull(&self, image: &Image) -> Result<PullResult, EngineError> {
let event_details = self.get_event_details();
let listeners_helper = ListenersHelper::new(&self.listeners);
if !self.does_image_exists(image) {
let info_message = format!("image {:?} does not exist in ECR {} repository", image, self.name());
info!("{}", info_message.as_str());
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(info_message.to_string()),
),
);
listeners_helper.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {
@@ -433,7 +524,14 @@ impl ContainerRegistry for ECR {
}
let info_message = format!("pull image {:?} from ECR {} repository", image, self.name());
info!("{}", info_message.as_str());
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(info_message.to_string()),
),
);
listeners_helper.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {
@@ -446,19 +544,7 @@ impl ContainerRegistry for ECR {
let _ = self.exec_docker_login()?;
let repository = match self.get_or_create_repository(image) {
Ok(r) => r,
_ => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"failed to create ECR repository for {} with image {:?}",
self.name_with_id(),
image,
),
));
}
};
let repository = self.get_or_create_repository(image)?;
let dest = format!("{}:{}", repository.repository_uri.unwrap(), image.tag.as_str());
@@ -469,23 +555,11 @@ impl ContainerRegistry for ECR {
fn push(&self, image: &Image, force_push: bool) -> Result<PushResult, EngineError> {
let _ = self.exec_docker_login()?;
let repository = match if force_push {
let repository = if force_push {
self.create_repository(image)
} else {
self.get_or_create_repository(image)
} {
Ok(r) => r,
_ => {
return Err(self.engine_error(
EngineErrorCause::Internal,
format!(
"failed to create ECR repository for {} with image {:?}",
self.name_with_id(),
image,
),
));
}
};
}?;
let dest = format!("{}:{}", repository.repository_uri.unwrap(), image.tag.as_str());
@@ -499,7 +573,13 @@ impl ContainerRegistry for ECR {
self.name()
);
info!("{}", info_message.as_str());
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
self.get_event_details(),
EventMessage::new_from_safe(info_message.to_string()),
),
);
listeners_helper.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {
@@ -522,7 +602,13 @@ impl ContainerRegistry for ECR {
self.name()
);
info!("{}", info_message.as_str());
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
self.get_event_details(),
EventMessage::new_from_safe(info_message.to_string()),
),
);
listeners_helper.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {

View File

@@ -1,8 +1,9 @@
use serde::{Deserialize, Serialize};
use crate::build_platform::Image;
use crate::error::{EngineError, EngineErrorCause, EngineErrorScope};
use crate::models::{Context, Listen};
use crate::errors::EngineError;
use crate::events::{EnvironmentStep, EventDetails, Stage};
use crate::models::{Context, Listen, QoveryIdentifier};
pub mod docker;
pub mod docker_hub;
@@ -27,15 +28,16 @@ pub trait ContainerRegistry: Listen {
fn pull(&self, image: &Image) -> Result<PullResult, EngineError>;
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),
fn get_event_details(&self) -> EventDetails {
let context = self.context();
EventDetails::new(
None,
QoveryIdentifier::from(context.organization_id().to_string()),
QoveryIdentifier::from(context.cluster_id().to_string()),
QoveryIdentifier::from(context.execution_id().to_string()),
None,
Stage::Environment(EnvironmentStep::Build),
self.to_transmitter(),
)
}
}

View File

@@ -8,7 +8,9 @@ use crate::container_registry::docker::{
docker_login, docker_manifest_inspect, docker_pull_image, docker_tag_and_push_image,
};
use crate::container_registry::{ContainerRegistry, Kind, PullResult, PushResult};
use crate::error::{EngineError, EngineErrorCause};
use crate::errors::{CommandError, EngineError};
use crate::events::{EngineEvent, EventMessage};
use crate::logger::{LogLevel, Logger};
use crate::models::{
Context, Listen, Listener, Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope,
};
@@ -18,7 +20,7 @@ use retry::Error::Operation;
use retry::OperationResult;
use rusoto_core::param::ToParam;
pub struct ScalewayCR {
pub struct ScalewayCR<'a> {
context: Context,
id: String,
name: String,
@@ -27,9 +29,10 @@ pub struct ScalewayCR {
secret_token: String,
zone: ScwZone,
listeners: Listeners,
logger: &'a dyn Logger,
}
impl ScalewayCR {
impl<'a> ScalewayCR<'a> {
pub fn new(
context: Context,
id: &str,
@@ -37,6 +40,7 @@ impl ScalewayCR {
secret_token: &str,
default_project_id: &str,
zone: ScwZone,
logger: &'a dyn Logger,
) -> ScalewayCR {
ScalewayCR {
context,
@@ -47,6 +51,7 @@ impl ScalewayCR {
secret_token: secret_token.to_string(),
zone,
listeners: Vec::new(),
logger,
}
}
@@ -84,9 +89,15 @@ impl ScalewayCR {
)) {
Ok(res) => res.namespaces,
Err(e) => {
error!(
"Error while interacting with Scaleway API (list_namespaces), error: {}, image: {}",
e, &image.name
self.logger.log(
LogLevel::Warning,
EngineEvent::Warning(
self.get_event_details(),
EventMessage::new(
"Error while interacting with Scaleway API (list_namespaces).".to_string(),
Some(format!("error: {}, image: {}", e, &image.name)),
),
),
);
return None;
}
@@ -121,9 +132,15 @@ impl ScalewayCR {
)) {
Ok(res) => res.images,
Err(e) => {
error!(
"Error while interacting with Scaleway API (list_images), error: {}, image: {}",
e, &image.name
self.logger.log(
LogLevel::Warning,
EngineEvent::Warning(
self.get_event_details(),
EventMessage::new(
"Error while interacting with Scaleway API (list_namespaces).".to_string(),
Some(format!("error: {}, image: {}", e, &image.name)),
),
),
);
return None;
}
@@ -143,13 +160,20 @@ impl ScalewayCR {
}
pub fn delete_image(&self, image: &Image) -> Result<scaleway_api_rs::models::ScalewayRegistryV1Image, EngineError> {
let event_details = self.get_event_details();
// https://developers.scaleway.com/en/products/registry/api/#delete-67dbf7
let image_to_delete = self.get_image(image);
if image_to_delete.is_none() {
let message = format!("While tyring to delete image {}, image doesn't exist", &image.name,);
error!("{}", message);
let err = EngineError::new_container_registry_image_doesnt_exist(
event_details.clone(),
image.name.to_string(),
None,
);
return Err(self.engine_error(EngineErrorCause::Internal, message));
self.logger.log(LogLevel::Error, EngineEvent::Error(err.clone(), None));
return Err(err);
}
let image_to_delete = image_to_delete.unwrap();
@@ -161,49 +185,62 @@ impl ScalewayCR {
)) {
Ok(res) => Ok(res),
Err(e) => {
let message = format!(
"Error while interacting with Scaleway API (delete_image), error: {}, image: {}",
e, &image.name
let err = EngineError::new_container_registry_delete_image_error(
event_details.clone(),
image.name.to_string(),
Some(CommandError::new(e.to_string(), None)),
);
error!("{}", message);
Err(self.engine_error(EngineErrorCause::Internal, message))
self.logger.log(LogLevel::Error, EngineEvent::Error(err.clone(), None));
Err(err)
}
}
}
fn push_image(&self, dest: String, image: &Image) -> Result<PushResult, EngineError> {
// https://www.scaleway.com/en/docs/deploy-an-image-from-registry-to-kubernetes-kapsule/
let event_details = self.get_event_details();
match docker_tag_and_push_image(
self.kind(),
self.get_docker_envs(),
image.name.clone(),
image.tag.clone(),
dest,
event_details.clone(),
self.logger(),
) {
Ok(_) => {}
Err(e) => {
return Err(self.engine_error(
EngineErrorCause::Internal,
e.message
.unwrap_or_else(|| "unknown error occurring during docker push".to_string()),
))
}
};
Err(e) => Err(EngineError::new_docker_push_image_error(
event_details,
image.name.to_string(),
dest.to_string(),
e,
)),
}
let result = retry::retry(Fibonacci::from_millis(10000).take(10), || {
match self.does_image_exists(image) {
true => OperationResult::Ok(&image),
false => {
warn!("image is not yet available on Scaleway Registry Namespace, retrying in a few seconds...");
self.logger.log(
LogLevel::Warning,
EngineEvent::Warning(
self.get_event_details(),
EventMessage::new_from_safe(
"Image is not yet available on Scaleway Registry Namespace, retrying in a few seconds...".to_string(),
),
),
);
OperationResult::Retry(())
}
}
});
let image_not_reachable = Err(self.engine_error(
EngineErrorCause::Internal,
"image has been pushed on Scaleway Registry Namespace but is not yet available after 4min. Please try to redeploy in a few minutes".to_string(),
let image_not_reachable = Err(EngineError::new_container_registry_image_unreachable_after_push(
event_details.clone(),
image.name.to_string(),
));
match result {
@@ -214,15 +251,22 @@ impl ScalewayCR {
}
fn pull_image(&self, dest: String, image: &Image) -> Result<PullResult, EngineError> {
match docker_pull_image(self.kind(), self.get_docker_envs(), dest) {
let event_details = self.get_event_details();
match docker_pull_image(
self.kind(),
self.get_docker_envs(),
dest,
event_details.clone(),
self.logger(),
) {
Ok(_) => {}
Err(e) => {
return Err(self.engine_error(
EngineErrorCause::Internal,
e.message
.unwrap_or_else(|| "unknown error occurring during docker pull".to_string()),
))
}
Err(e) => Err(EngineError::new_docker_pull_image_error(
event_details,
image.name.to_string(),
dest.to_string(),
e,
)),
};
Ok(PullResult::Some(image.clone()))
@@ -246,13 +290,17 @@ impl ScalewayCR {
)) {
Ok(res) => Ok(res),
Err(e) => {
let message = format!(
"Error while interacting with Scaleway API (create_namespace), error: {}, image: {}",
e, &image.name
let error = EngineError::new_container_registry_namespace_creation_error(
event_details.clone(),
repository_name.to_string(),
self.name_with_id(),
CommandError::new(e.to_string(), Some("Can't create SCW repository".to_string())),
);
error!("{}", message);
Err(self.engine_error(EngineErrorCause::Internal, message))
self.logger
.log(LogLevel::Error, EngineEvent::Error(error.clone(), None));
Err(error)
}
}
}
@@ -262,15 +310,19 @@ impl ScalewayCR {
image: &Image,
) -> Result<scaleway_api_rs::models::ScalewayRegistryV1Namespace, EngineError> {
// https://developers.scaleway.com/en/products/registry/api/#delete-c1ac9b
let event_details = self.get_event_details();
let registry_to_delete = self.get_registry_namespace(image);
if registry_to_delete.is_none() {
let message = format!(
"While tyring to delete registry namespace for image {}, registry namespace doesn't exist",
&image.name,
let error = EngineError::new_container_registry_repository_doesnt_exist(
event_details.clone(),
image.registry_name.unwrap_or("unknown".to_string()),
None,
);
error!("{}", message);
return Err(self.engine_error(EngineErrorCause::Internal, message));
self.logger
.log(LogLevel::Error, EngineEvent::Error(error.clone(), None));
return Err(error);
}
let registry_to_delete = registry_to_delete.unwrap();
@@ -282,13 +334,16 @@ impl ScalewayCR {
)) {
Ok(res) => Ok(res),
Err(e) => {
let message = format!(
"Error while interacting with Scaleway API (delete_namespace), error: {}, image: {}",
e, &image.name
let error = EngineError::new_container_registry_delete_repository_error(
event_details.clone(),
image.registry_name.unwrap_or("unknown".to_string()),
Some(CommandError::new(e.to_string(), None)),
);
error!("{}", message);
Err(self.engine_error(EngineErrorCause::Internal, message))
self.logger
.log(LogLevel::Error, EngineEvent::Error(error.clone(), None));
return Err(error);
}
}
}
@@ -298,9 +353,16 @@ impl ScalewayCR {
image: &Image,
) -> Result<scaleway_api_rs::models::ScalewayRegistryV1Namespace, EngineError> {
// check if the repository already exists
let event_details = self.get_event_details();
let registry_namespace = self.get_registry_namespace(&image);
if let Some(namespace) = registry_namespace {
info!("Scaleway registry namespace {} already exists", image.name.as_str());
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(format!("SCW repository {} already exists", image.name.as_str())),
),
);
return Ok(namespace);
}
@@ -319,21 +381,20 @@ impl ScalewayCR {
}
fn exec_docker_login(&self, registry_url: &String) -> Result<(), EngineError> {
let event_details = self.get_event_details();
if docker_login(
Kind::ScalewayCr,
self.get_docker_envs(),
self.login.clone(),
self.secret_token.clone(),
registry_url.clone(),
event_details.clone(),
self.logger(),
)
.is_err()
{
return Err(self.engine_error(
EngineErrorCause::User(
"Your Scaleway account seems to be no longer valid (bad Credentials). \
Please contact your Organization administrator to fix or change the Credentials.",
),
format!("failed to login to Scaleway {}", self.name_with_id()),
return Err(EngineError::new_client_invalid_cloud_provider_credentials(
event_details,
));
};
@@ -379,6 +440,7 @@ impl ContainerRegistry for ScalewayCR {
}
fn does_image_exists(&self, image: &Image) -> bool {
let event_details = self.get_event_details();
let registry_url = image
.registry_url
.as_ref()
@@ -391,6 +453,8 @@ impl ContainerRegistry for ScalewayCR {
self.login.clone(),
self.secret_token.clone(),
registry_url.clone(),
event_details.clone(),
self.logger(),
) {
return false;
}
@@ -401,11 +465,14 @@ impl ContainerRegistry for ScalewayCR {
image.name.clone(),
image.tag.clone(),
registry_url,
event_details.clone(),
self.logger(),
)
.is_some()
}
fn pull(&self, image: &Image) -> Result<PullResult, EngineError> {
let event_details = self.get_event_details();
let listeners_helper = ListenersHelper::new(&self.listeners);
let mut image = image.clone();
@@ -413,10 +480,17 @@ impl ContainerRegistry for ScalewayCR {
match self.get_or_create_registry_namespace(&image) {
Ok(registry) => {
info!(
"Scaleway registry namespace for {} has been created",
image.name.as_str()
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(format!(
"Scaleway registry namespace for {} has been created",
image.name.as_str()
)),
),
);
image.registry_name = Some(image.name.clone()); // Note: Repository namespace should have the same name as the image name
image.registry_url = registry.endpoint.clone();
image.registry_secret = Some(self.secret_token.clone());
@@ -424,18 +498,23 @@ impl ContainerRegistry for ScalewayCR {
registry_url = registry.endpoint.unwrap_or_else(|| "undefined".to_string());
}
Err(e) => {
error!(
"Scaleway registry namespace for {} cannot be created, error: {:?}",
image.name.as_str(),
e
);
self.logger.log(LogLevel::Error, EngineEvent::Error(e.clone(), None));
return Err(e);
}
}
if !self.does_image_exists(&image) {
let info_message = format!("image {:?} does not exist in SCR {} repository", image, self.name());
info!("{}", info_message.as_str());
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(format!(
"Image {:?} does not exist in SCR {} repository",
image,
self.name()
)),
),
);
listeners_helper.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {
@@ -450,7 +529,14 @@ impl ContainerRegistry for ScalewayCR {
}
let info_message = format!("pull image {:?} from SCR {} repository", image, self.name());
info!("{}", info_message.as_str());
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(info_message.to_string()),
),
);
listeners_helper.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {
@@ -470,16 +556,13 @@ impl ContainerRegistry for ScalewayCR {
}
fn push(&self, image: &Image, force_push: bool) -> Result<PushResult, EngineError> {
let event_details = self.get_event_details();
let mut image = image.clone();
let registry_url: String;
let registry_name: String;
match self.get_or_create_registry_namespace(&image) {
Ok(registry) => {
info!(
"Scaleway registry namespace for {} has been created",
image.name.as_str()
);
image.registry_name = Some(image.name.clone()); // Note: Repository namespace should have the same name as the image name
image.registry_url = registry.endpoint.clone();
image.registry_secret = Some(self.secret_token.clone());
@@ -488,11 +571,7 @@ impl ContainerRegistry for ScalewayCR {
registry_name = registry.name.unwrap();
}
Err(e) => {
error!(
"Scaleway registry namespace for {} cannot be created, error: {:?}",
image.name.as_str(),
e
);
self.logger.log(LogLevel::Error, EngineEvent::Error(e.clone(), None));
return Err(e);
}
}
@@ -510,7 +589,13 @@ impl ContainerRegistry for ScalewayCR {
image, registry_name,
);
info!("{}", info_message.as_str());
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(info_message.to_string()),
),
);
listeners_helper.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {
@@ -530,7 +615,13 @@ impl ContainerRegistry for ScalewayCR {
self.name()
);
info!("{}", info_message.as_str());
self.logger.log(
LogLevel::Info,
EngineEvent::Info(
event_details.clone(),
EventMessage::new_from_safe(info_message.to_string()),
),
);
listeners_helper.deployment_in_progress(ProgressInfo::new(
ProgressScope::Application {

View File

@@ -1,7 +1,7 @@
use std::net::Ipv4Addr;
use crate::dns_provider::{DnsProvider, Kind};
use crate::error::{EngineError, EngineErrorCause};
use crate::errors::EngineError;
use crate::models::{Context, Domain};
pub struct Cloudflare {

View File

@@ -1,8 +1,8 @@
use std::net::Ipv4Addr;
use crate::errors::EngineError;
use serde::{Deserialize, Serialize};
use crate::error::{EngineError, EngineErrorCause, EngineErrorScope};
use crate::models::{Context, Domain};
pub mod cloudflare;
@@ -21,17 +21,6 @@ pub trait DnsProvider {
fn domain(&self) -> &Domain;
fn resolvers(&self) -> Vec<Ipv4Addr>;
fn is_valid(&self) -> Result<(), EngineError>;
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::DnsProvider(self.id().to_string(), self.name().to_string())
}
fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError {
EngineError::new(
cause,
self.engine_error_scope(),
self.context().execution_id(),
Some(message),
)
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]

View File

@@ -4,7 +4,7 @@ use crate::build_platform::BuildPlatform;
use crate::cloud_provider::CloudProvider;
use crate::container_registry::ContainerRegistry;
use crate::dns_provider::DnsProvider;
use crate::error::EngineError;
use crate::errors::EngineError;
use crate::logger::Logger;
use crate::models::Context;
use crate::session::Session;

View File

@@ -68,6 +68,7 @@ pub enum EngineErrorCause {
}
#[derive(Debug)]
#[deprecated(note = "errors.CommandError to be used instead")]
pub struct SimpleError {
pub kind: SimpleErrorKind,
pub message: Option<String>,

View File

@@ -204,6 +204,46 @@ pub enum Tag {
DatabaseFailedToStartAfterSeveralRetries,
/// RouterFailedToDeploy: represents an error while trying to deploy a router.
RouterFailedToDeploy,
/// CloudProviderClientInvalidCredentials: represents an error where client credentials for a cloud providers appear to be invalid.
CloudProviderClientInvalidCredentials,
/// BuilderDockerCannotFindAnyDockerfile: represents an error when trying to get a Dockerfile.
BuilderDockerCannotFindAnyDockerfile,
/// BuilderDockerCannotReadDockerfile: represents an error while trying to read Dockerfile.
BuilderDockerCannotReadDockerfile,
/// BuilderDockerCannotExtractEnvVarsFromDockerfile: represents an error while trying to extract ENV vars from Dockerfile.
BuilderDockerCannotExtractEnvVarsFromDockerfile,
/// BuilderDockerCannotBuildContainerImage: represents an error while trying to build Docker container image.
BuilderDockerCannotBuildContainerImage,
/// BuilderBuildpackInvalidLanguageFormat: represents an error where buildback requested language has wrong format.
BuilderBuildpackInvalidLanguageFormat,
/// BuilderBuildpackCannotBuildContainerImage: represents an error while trying to build container image with Buildpack.
BuilderBuildpackCannotBuildContainerImage,
/// BuilderGetBuildError: represents an error when builder is trying to get parent build.
BuilderGetBuildError,
/// BuilderCloningRepositoryError: represents an error when builder is trying to clone a git repository.
BuilderCloningRepositoryError,
/// DockerPushImageError: represents an error when trying to push a docker image.
DockerPushImageError,
/// DockerPullImageError: represents an error when trying to pull a docker image.
DockerPullImageError,
/// ContainerRegistryRepositoryCreationError: represents an error when trying to create a repository.
ContainerRegistryRepositoryCreationError,
/// ContainerRegistryRepositorySetLifecycleError: represents an error when trying to set repository lifecycle policy.
ContainerRegistryRepositorySetLifecycleError,
/// ContainerRegistryGetCredentialsError: represents an error when trying to get container registry credentials.
ContainerRegistryGetCredentialsError,
/// ContainerRegistryDeleteImageError: represents an error while trying to delete an image.
ContainerRegistryDeleteImageError,
/// ContainerRegistryImageDoesntExist: represents an error, image doesn't exist in the registry.
ContainerRegistryImageDoesntExist,
/// ContainerRegistryImageUnreachableAfterPush: represents an error when image has been pushed but is unreachable.
ContainerRegistryImageUnreachableAfterPush,
/// ContainerRegistryRepositoryDoesntExist: represents an error, repository doesn't exist.
ContainerRegistryRepositoryDoesntExist,
/// ContainerRegistryDeleteRepositoryError: represents an error while trying to delete a repository.
ContainerRegistryDeleteRepositoryError,
/// NotImplementedError: represents an error where feature / code has not been implemented yet.
NotImplementedError,
}
#[derive(Clone, Debug)]
@@ -1852,4 +1892,512 @@ impl EngineError {
None,
)
}
/// Creates new error when trying to connect to user's account with its credentials.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
pub fn new_client_invalid_cloud_provider_credentials(event_details: EventDetails) -> EngineError {
let message = "Your cloud provider account seems to be no longer valid (bad credentials).";
EngineError::new(
event_details,
Tag::CloudProviderClientInvalidCredentials,
message.to_string(),
message.to_string(),
None,
None,
Some("Please contact your Organization administrator to fix or change the Credentials.".to_string()),
)
}
/// Creates new error when trying to read Dockerfile content.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
/// * `dockerfile_path`: Dockerfile path.
/// * `raw_error`: Raw error message.
pub fn new_docker_cannot_read_dockerfile(
event_details: EventDetails,
dockerfile_path: String,
raw_error: CommandError,
) -> EngineError {
let message = format!("Can't read Dockerfile `{}`.", dockerfile_path);
EngineError::new(
event_details,
Tag::BuilderDockerCannotReadDockerfile,
message.to_string(),
message.to_string(),
Some(raw_error),
None,
None,
)
}
/// Creates new error when trying to extract env vars from Dockerfile.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
/// * `dockerfile_path`: Dockerfile path.
/// * `raw_error`: Raw error message.
pub fn new_docker_cannot_extract_env_vars_from_dockerfile(
event_details: EventDetails,
dockerfile_path: String,
raw_error: CommandError,
) -> EngineError {
let message = format!("Can't extract ENV vars from Dockerfile `{}`.", dockerfile_path);
EngineError::new(
event_details,
Tag::BuilderDockerCannotExtractEnvVarsFromDockerfile,
message.to_string(),
message.to_string(),
Some(raw_error),
None,
None,
)
}
/// Creates new error when trying to build Docker container.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
/// * `container_image_name`: Container image name.
/// * `raw_error`: Raw error message.
pub fn new_docker_cannot_build_container_image(
event_details: EventDetails,
container_image_name: String,
raw_error: CommandError,
) -> EngineError {
let message = format!("Error while building container image `{}`.", container_image_name);
EngineError::new(
event_details,
Tag::BuilderDockerCannotBuildContainerImage,
message.to_string(),
message.to_string(),
Some(raw_error),
None,
Some("It looks like there is something wrong in your Dockerfile. Try building the application locally with `docker build --no-cache`.".to_string()),
)
}
/// Creates new error when trying to get Dockerfile.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
/// * `location_path`: Dockerfile location path.
pub fn new_docker_cannot_find_dockerfile(event_details: EventDetails, location_path: String) -> EngineError {
let message = format!("Dockerfile not found at location `{}`.", location_path);
EngineError::new(
event_details,
Tag::BuilderDockerCannotFindAnyDockerfile,
message.to_string(),
message.to_string(),
None,
None,
Some("Your Dockerfile is not present at the specified location, check your settings.".to_string()),
)
}
/// Creates new error buildpack invalid language format.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
/// * `requested_language`: Requested language.
pub fn new_buildpack_invalid_language_format(
event_details: EventDetails,
requested_language: String,
) -> EngineError {
let message = format!(
"Cannot build: Invalid buildpacks language format: `{}`.",
requested_language
);
EngineError::new(
event_details,
Tag::BuilderBuildpackInvalidLanguageFormat,
message.to_string(),
message.to_string(),
None,
None,
Some("Expected format `builder[@version]`.".to_string()),
)
}
/// Creates new error when trying to build container.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
/// * `container_image_name`: Container image name.
/// * `raw_error`: Raw error message.
pub fn new_buildpack_cannot_build_container_image(
event_details: EventDetails,
container_image_name: String,
builders: Vec<String>,
raw_error: CommandError,
) -> EngineError {
let message = "Cannot find a builder to build application.";
EngineError::new(
event_details,
Tag::BuilderBuildpackCannotBuildContainerImage,
message.to_string(),
message.to_string(),
Some(raw_error),
None,
Some(format!(
"Qovery can't build your container image {} with one of the following builders: {}. Please do provide a valid Dockerfile to build your application or contact the support.",
container_image_name,
builders.join(", ")
),),
)
}
/// Creates new error when trying to get build.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
/// * `commit_id`: Commit ID of build to be retrieved.
/// * `raw_error`: Raw error message.
pub fn new_builder_get_build_error(
event_details: EventDetails,
commit_id: String,
raw_error: CommandError,
) -> EngineError {
let message = format!("Error while trying to get build with commit ID: `{}`.", commit_id);
EngineError::new(
event_details,
Tag::BuilderGetBuildError,
message.to_string(),
message.to_string(),
Some(raw_error),
None,
None,
)
}
/// Creates new error when builder is trying to clone a git repository.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
/// * `repository_url`: Repository URL.
/// * `raw_error`: Raw error message.
pub fn new_builder_clone_repository_error(
event_details: EventDetails,
repository_url: String,
raw_error: CommandError,
) -> EngineError {
let message = format!("Error while cloning repository `{}`.", repository_url);
EngineError::new(
event_details,
Tag::BuilderCloningRepositoryError,
message.to_string(),
message.to_string(),
Some(raw_error),
None,
None,
)
}
/// Creates new error when something went wrong because it's not implemented.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
pub fn new_not_implemented_error(event_details: EventDetails) -> EngineError {
let message = "Error, something went wrong because it's not implemented.";
EngineError::new(
event_details,
Tag::NotImplementedError,
message.to_string(),
message.to_string(),
None,
None,
None,
)
}
/// Creates new error when trying to push a Docker image.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
/// * `image_name`: Docker image name.
/// * `repository_url`: Repository URL.
/// * `raw_error`: Raw error message.
pub fn new_docker_push_image_error(
event_details: EventDetails,
image_name: String,
repository_url: String,
raw_error: CommandError,
) -> EngineError {
let message = format!(
"Error, trying to push Docker image `{}` to repository `{}`.",
image_name, repository_url
);
EngineError::new(
event_details,
Tag::DockerPushImageError,
message.to_string(),
message.to_string(),
Some(raw_error),
None,
None,
)
}
/// Creates new error when trying to pull a Docker image.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
/// * `image_name`: Docker image name.
/// * `repository_url`: Repository URL.
/// * `raw_error`: Raw error message.
pub fn new_docker_pull_image_error(
event_details: EventDetails,
image_name: String,
repository_url: String,
raw_error: CommandError,
) -> EngineError {
let message = format!(
"Error, trying to pull Docker image `{}` from repository `{}`.",
image_name, repository_url
);
EngineError::new(
event_details,
Tag::DockerPullImageError,
message.to_string(),
message.to_string(),
Some(raw_error),
None,
None,
)
}
/// Creates new error when trying to create a new container registry namespace.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
/// * `repository_name`: Container repository name.
/// * `registry_name`: Registry to be created.
/// * `raw_error`: Raw error message.
pub fn new_container_registry_namespace_creation_error(
event_details: EventDetails,
repository_name: String,
registry_name: String,
raw_error: CommandError,
) -> EngineError {
let message = format!(
"Error, trying to create registry `{}` in `{}`.",
registry_name, repository_name
);
EngineError::new(
event_details,
Tag::ContainerRegistryRepositoryCreationError,
message.to_string(),
message.to_string(),
Some(raw_error),
None,
None,
)
}
/// Creates new error when trying to set container repository lifecycle policy.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
/// * `repository_name`: Repository name.
/// * `raw_error`: Raw error message.
pub fn new_container_registry_repository_set_lifecycle_policy_error(
event_details: EventDetails,
repository_name: String,
raw_error: CommandError,
) -> EngineError {
let message = format!(
"Error, trying to set lifecycle policy repository `{}`.",
repository_name,
);
EngineError::new(
event_details,
Tag::ContainerRegistryRepositorySetLifecycleError,
message.to_string(),
message.to_string(),
Some(raw_error),
None,
None,
)
}
/// Creates new error when trying to get container registry credentials.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
/// * `repository_name`: Repository name.
pub fn new_container_registry_get_credentials_error(
event_details: EventDetails,
repository_name: String,
) -> EngineError {
let message = format!(
"Failed to retrieve credentials and endpoint URL from container registry `{}`.",
repository_name,
);
EngineError::new(
event_details,
Tag::ContainerRegistryGetCredentialsError,
message.to_string(),
message.to_string(),
None,
None,
None,
)
}
/// Creates new error when trying to delete an image.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
/// * `image_name`: Image name.
/// * `raw_error`: Raw error message.
pub fn new_container_registry_delete_image_error(
event_details: EventDetails,
image_name: String,
raw_error: Option<CommandError>,
) -> EngineError {
let message = format!("Failed to delete image `{}`.", image_name,);
EngineError::new(
event_details,
Tag::ContainerRegistryDeleteImageError,
message.to_string(),
message.to_string(),
raw_error,
None,
None,
)
}
/// Creates new error when trying to get image from a registry.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
/// * `image_name`: Image name.
pub fn new_container_registry_image_doesnt_exist(
event_details: EventDetails,
image_name: String,
raw_error: Option<CommandError>,
) -> EngineError {
let message = format!("Image `{}` doesn't exists.", image_name,);
EngineError::new(
event_details,
Tag::ContainerRegistryImageDoesntExist,
message.to_string(),
message.to_string(),
raw_error,
None,
None,
)
}
/// Creates new error when image is unreachable after push.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
/// * `image_name`: Image name.
/// * `raw_error`: Raw error message.
pub fn new_container_registry_image_unreachable_after_push(
event_details: EventDetails,
image_name: String,
) -> EngineError {
let message = format!(
"Image `{}` has been pushed on registry namespace but is not yet available after some time.",
image_name,
);
EngineError::new(
event_details,
Tag::ContainerRegistryImageUnreachableAfterPush,
message.to_string(),
message.to_string(),
None,
None,
Some("Please try to redeploy in a few minutes.".to_string()),
)
}
/// Creates new error when trying to get image from a registry.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
/// * `repository_name`: Repository name.
pub fn new_container_registry_repository_doesnt_exist(
event_details: EventDetails,
repository_name: String,
raw_error: Option<CommandError>,
) -> EngineError {
let message = format!("Repository `{}` doesn't exists.", repository_name,);
EngineError::new(
event_details,
Tag::ContainerRegistryRepositoryDoesntExist,
message.to_string(),
message.to_string(),
raw_error,
None,
None,
)
}
/// Creates new error when trying to delete repository.
///
/// Arguments:
///
/// * `event_details`: Error linked event details.
/// * `repository_name`: Repository name.
/// * `raw_error`: Raw error message.
pub fn new_container_registry_delete_repository_error(
event_details: EventDetails,
repository_name: String,
raw_error: Option<CommandError>,
) -> EngineError {
let message = format!("Failed to delete repository `{}`.", repository_name,);
EngineError::new(
event_details,
Tag::ContainerRegistryDeleteRepositoryError,
message.to_string(),
message.to_string(),
raw_error,
None,
None,
)
}
}

View File

@@ -5,7 +5,7 @@ pub mod io;
extern crate url;
use crate::cloud_provider::Kind;
use crate::errors::EngineError;
use crate::errors::{CommandError, EngineError};
use crate::models::QoveryIdentifier;
use std::fmt::{Display, Formatter};
@@ -142,6 +142,12 @@ impl EventMessage {
}
}
impl From<CommandError> for EventMessage {
fn from(e: CommandError) -> Self {
EventMessage::new(e.message_raw(), e.message_safe())
}
}
impl Display for EventMessage {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(self.message(EventMessageVerbosity::SafeOnly).as_str()) // By default, expose only the safe message.
@@ -449,7 +455,8 @@ mod tests {
use crate::cloud_provider::Kind::Aws;
use crate::errors::{CommandError, EngineError};
use crate::events::{
EngineEvent, EnvironmentStep, EventDetails, EventMessage, InfrastructureStep, Stage, Transmitter,
EngineEvent, EnvironmentStep, EventDetails, EventMessage, EventMessageVerbosity, InfrastructureStep, Stage,
Transmitter,
};
use crate::models::QoveryIdentifier;

View File

@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use crate::error::{EngineError, EngineErrorCause, EngineErrorScope};
use crate::errors::EngineError;
use crate::models::{Context, StringPath};
use std::fs::File;
@@ -21,17 +21,6 @@ pub trait ObjectStorage {
fn delete_bucket(&self, bucket_name: &str) -> Result<(), EngineError>;
fn get(&self, bucket_name: &str, object_key: &str, use_cache: bool) -> Result<(StringPath, File), EngineError>;
fn put(&self, bucket_name: &str, object_key: &str, file_path: &str) -> Result<(), EngineError>;
fn engine_error_scope(&self) -> EngineErrorScope {
EngineErrorScope::ObjectStorage(self.id().to_string(), self.name().to_string())
}
fn engine_error(&self, cause: EngineErrorCause, message: String) -> EngineError {
EngineError::new(
cause,
self.engine_error_scope(),
self.context().execution_id(),
Some(message),
)
}
}
#[derive(Serialize, Deserialize, Clone)]

View File

@@ -5,6 +5,7 @@ use std::path::Path;
use std::str::FromStr;
use crate::cloud_provider::aws::regions::AwsRegion;
use crate::errors::EngineError;
use rusoto_core::credential::StaticProvider;
use rusoto_core::{Client, HttpClient, Region as RusotoRegion};
use rusoto_s3::{
@@ -14,7 +15,6 @@ use rusoto_s3::{
};
use tokio::io;
use crate::error::{EngineError, EngineErrorCause};
use crate::models::{Context, StringPath};
use crate::object_storage::{Kind, ObjectStorage};
use crate::runtime::block_on;

View File

@@ -3,10 +3,10 @@ use std::fs::File;
use std::path::Path;
use crate::cloud_provider::scaleway::application::ScwZone;
use crate::error::{EngineError, EngineErrorCause};
use crate::models::{Context, StringPath};
use crate::object_storage::{Kind, ObjectStorage};
use crate::errors::EngineError;
use crate::runtime::block_on;
use rusoto_core::{Client, HttpClient, Region as RusotoRegion};
use rusoto_credential::StaticProvider;

View File

@@ -12,7 +12,7 @@ use rusoto_s3::{
use tokio::io;
use crate::cloud_provider::digitalocean::application::DoRegion;
use crate::error::{EngineError, EngineErrorCause};
use crate::errors::EngineError;
use crate::models::{Context, StringPath};
use crate::object_storage::{Kind, ObjectStorage};
use crate::runtime;

View File

@@ -6,8 +6,7 @@ use crate::cloud_provider::kubernetes::Kubernetes;
use crate::cloud_provider::service::{Application, Service};
use crate::container_registry::PushResult;
use crate::engine::Engine;
use crate::error::EngineError;
use crate::errors::EngineError as NewEngineError;
use crate::errors::EngineError;
use crate::models::{
Action, Environment, EnvironmentAction, EnvironmentError, ListenersHelper, ProgressInfo, ProgressLevel,
ProgressScope,
@@ -28,7 +27,7 @@ impl<'a> Transaction<'a> {
}
}
pub fn create_kubernetes(&mut self, kubernetes: &'a dyn Kubernetes) -> Result<(), NewEngineError> {
pub fn create_kubernetes(&mut self, kubernetes: &'a dyn Kubernetes) -> Result<(), EngineError> {
match kubernetes.is_valid() {
Ok(_) => {
self.steps.push(Step::CreateKubernetes(kubernetes));
@@ -38,7 +37,7 @@ impl<'a> Transaction<'a> {
}
}
pub fn pause_kubernetes(&mut self, kubernetes: &'a dyn Kubernetes) -> Result<(), NewEngineError> {
pub fn pause_kubernetes(&mut self, kubernetes: &'a dyn Kubernetes) -> Result<(), EngineError> {
match kubernetes.is_valid() {
Ok(_) => {
self.steps.push(Step::PauseKubernetes(kubernetes));
@@ -48,7 +47,7 @@ impl<'a> Transaction<'a> {
}
}
pub fn delete_kubernetes(&mut self, kubernetes: &'a dyn Kubernetes) -> Result<(), NewEngineError> {
pub fn delete_kubernetes(&mut self, kubernetes: &'a dyn Kubernetes) -> Result<(), EngineError> {
match kubernetes.is_valid() {
Ok(_) => {
self.steps.push(Step::DeleteKubernetes(kubernetes));