mirror of
https://github.com/jlengrand/engine.git
synced 2026-03-10 08:11:21 +00:00
refactor: ease command error message safe / unsafe logic
This commit is contained in:
@@ -6,7 +6,7 @@ use crate::cloud_provider::helm::{
|
||||
};
|
||||
use crate::cloud_provider::qovery::{get_qovery_app_version, EngineLocation, QoveryAgent, QoveryAppName, QoveryEngine};
|
||||
use crate::cmd::kubectl::{kubectl_delete_crash_looping_pods, kubectl_exec_get_daemonset, kubectl_exec_with_output};
|
||||
use crate::errors::CommandError;
|
||||
use crate::errors::{CommandError, CommandErrorMessageVerbosity};
|
||||
use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::File;
|
||||
@@ -66,10 +66,10 @@ pub fn aws_helm_charts(
|
||||
let content_file = match File::open(&qovery_terraform_config_file) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
let message_safe = "Can't deploy helm chart as Qovery terraform config file has not been rendered by Terraform. Are you running it in dry run mode?";
|
||||
return Err(CommandError::new(
|
||||
format!("{}, error: {:?}", message_safe.to_string(), e),
|
||||
Some(message_safe.to_string()),
|
||||
"Can't deploy helm chart as Qovery terraform config file has not been rendered by Terraform. Are you running it in dry run mode?".to_string(),
|
||||
Some(e.to_string()),
|
||||
true,
|
||||
));
|
||||
}
|
||||
};
|
||||
@@ -79,13 +79,13 @@ pub fn aws_helm_charts(
|
||||
let qovery_terraform_config: AwsQoveryTerraformConfig = match serde_json::from_reader(reader) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
let message_safe = format!(
|
||||
"Error while parsing terraform config file {}",
|
||||
qovery_terraform_config_file
|
||||
);
|
||||
return Err(CommandError::new(
|
||||
format!("{}, error: {:?}", message_safe.to_string(), e),
|
||||
Some(message_safe.to_string()),
|
||||
format!(
|
||||
"Error while parsing terraform config file {}",
|
||||
qovery_terraform_config_file
|
||||
),
|
||||
Some(e.to_string()),
|
||||
true,
|
||||
));
|
||||
}
|
||||
};
|
||||
@@ -1344,16 +1344,14 @@ impl AwsVpcCniChart {
|
||||
_ => Ok(false),
|
||||
},
|
||||
},
|
||||
Err(e) => {
|
||||
let message_safe = format!(
|
||||
Err(e) => Err(CommandError::new(
|
||||
format!(
|
||||
"Error while getting daemonset info for chart {}, won't deploy CNI chart.",
|
||||
&self.chart_info.name
|
||||
);
|
||||
Err(CommandError::new(
|
||||
format!("{}, error: {:?}", message_safe, e),
|
||||
Some(message_safe),
|
||||
))
|
||||
}
|
||||
),
|
||||
Some(e.message(CommandErrorMessageVerbosity::FullDetails)),
|
||||
true,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ use crate::cmd::terraform::{terraform_exec, terraform_init_validate_plan_apply,
|
||||
use crate::deletion_utilities::{get_firsts_namespaces_to_delete, get_qovery_managed_namespaces};
|
||||
use crate::dns_provider;
|
||||
use crate::dns_provider::DnsProvider;
|
||||
use crate::errors::{CommandError, EngineError};
|
||||
use crate::errors::{CommandError, CommandErrorMessageVerbosity, EngineError};
|
||||
use crate::events::Stage::Infrastructure;
|
||||
use crate::events::{EngineEvent, EnvironmentStep, EventDetails, EventMessage, InfrastructureStep, Stage, Transmitter};
|
||||
use crate::logger::{LogLevel, Logger};
|
||||
@@ -837,7 +837,10 @@ impl<'a> EKS<'a> {
|
||||
LogLevel::Error,
|
||||
EngineEvent::Deploying(
|
||||
event_details.clone(),
|
||||
EventMessage::new("Error trying to get kubernetes events".to_string(), Some(err.message())),
|
||||
EventMessage::new(
|
||||
"Error trying to get kubernetes events".to_string(),
|
||||
Some(err.message(CommandErrorMessageVerbosity::FullDetails)),
|
||||
),
|
||||
),
|
||||
),
|
||||
};
|
||||
@@ -971,10 +974,7 @@ impl<'a> EKS<'a> {
|
||||
for metric in metrics.items {
|
||||
match metric.value.parse::<i32>() {
|
||||
Ok(job_count) if job_count > 0 => current_engine_jobs += 1,
|
||||
Err(e) => {
|
||||
let safe_message = "Error while looking at the API metric value";
|
||||
return OperationResult::Retry(EngineError::new_cannot_get_k8s_api_custom_metrics(event_details.clone(), CommandError::new(format!("{}, error: {}", safe_message, e.to_string()), Some(safe_message.to_string()))));
|
||||
}
|
||||
Err(e) => OperationResult::Retry(EngineError::new_cannot_get_k8s_api_custom_metrics(event_details.clone(), CommandError::new("Error while looking at the API metric value".to_string(), Some(e.to_string()), true))),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -1108,12 +1108,14 @@ impl<'a> EKS<'a> {
|
||||
let kubernetes_config_file_path = match self.get_kubeconfig_file_path() {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
let safe_message = "Skipping Kubernetes uninstall because it can't be reached.";
|
||||
self.logger().log(
|
||||
LogLevel::Warning,
|
||||
EngineEvent::Deleting(
|
||||
event_details.clone(),
|
||||
EventMessage::new(safe_message.to_string(), Some(e.message())),
|
||||
EventMessage::new(
|
||||
"Skipping Kubernetes uninstall because it can't be reached.".to_string(),
|
||||
Some(e.message(CommandErrorMessageVerbosity::FullDetails)),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1201,7 +1203,11 @@ impl<'a> EKS<'a> {
|
||||
),
|
||||
),
|
||||
Err(e) => {
|
||||
if !(e.message().contains("not found")) {
|
||||
if !(e
|
||||
.message(CommandErrorMessageVerbosity::FullDetails)
|
||||
.to_lowercase()
|
||||
.contains("not found"))
|
||||
{
|
||||
self.logger().log(
|
||||
LogLevel::Error,
|
||||
EngineEvent::Deleting(
|
||||
@@ -1218,15 +1224,17 @@ impl<'a> EKS<'a> {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let message_safe = format!(
|
||||
"Error while getting all namespaces for Kubernetes cluster {}",
|
||||
self.name_with_id(),
|
||||
);
|
||||
self.logger().log(
|
||||
LogLevel::Error,
|
||||
EngineEvent::Deleting(
|
||||
event_details.clone(),
|
||||
EventMessage::new(message_safe, Some(e.message())),
|
||||
EventMessage::new(
|
||||
format!(
|
||||
"Error while getting all namespaces for Kubernetes cluster {}",
|
||||
self.name_with_id(),
|
||||
),
|
||||
Some(e.message(CommandErrorMessageVerbosity::FullDetails)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1299,7 +1307,10 @@ impl<'a> EKS<'a> {
|
||||
LogLevel::Error,
|
||||
EngineEvent::Deleting(
|
||||
event_details.clone(),
|
||||
EventMessage::new(message_safe, Some(e.message())),
|
||||
EventMessage::new(
|
||||
message_safe,
|
||||
Some(e.message(CommandErrorMessageVerbosity::FullDetails)),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -1307,7 +1318,11 @@ impl<'a> EKS<'a> {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if !(e.message().contains("not found")) {
|
||||
if !(e
|
||||
.message(CommandErrorMessageVerbosity::FullDetails)
|
||||
.to_lowercase()
|
||||
.contains("not found"))
|
||||
{
|
||||
self.logger().log(
|
||||
LogLevel::Error,
|
||||
EngineEvent::Deleting(
|
||||
@@ -1346,7 +1361,11 @@ impl<'a> EKS<'a> {
|
||||
),
|
||||
),
|
||||
Err(e) => {
|
||||
if !(e.message().contains("not found")) {
|
||||
if !(e
|
||||
.message(CommandErrorMessageVerbosity::FullDetails)
|
||||
.to_lowercase()
|
||||
.contains("not found"))
|
||||
{
|
||||
self.logger().log(
|
||||
LogLevel::Error,
|
||||
EngineEvent::Deleting(
|
||||
@@ -1402,16 +1421,16 @@ impl<'a> EKS<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let message_safe = "Unable to get helm list";
|
||||
self.logger().log(
|
||||
LogLevel::Error,
|
||||
EngineEvent::Deleting(
|
||||
event_details.clone(),
|
||||
EventMessage::new(message_safe.to_string(), Some(e.message())),
|
||||
Err(e) => self.logger().log(
|
||||
LogLevel::Error,
|
||||
EngineEvent::Deleting(
|
||||
event_details.clone(),
|
||||
EventMessage::new(
|
||||
"Unable to get helm list".to_string(),
|
||||
Some(e.message(CommandErrorMessageVerbosity::FullDetails)),
|
||||
),
|
||||
)
|
||||
}
|
||||
),
|
||||
),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1456,7 +1475,7 @@ impl<'a> EKS<'a> {
|
||||
)),
|
||||
Err(retry::Error::Internal(msg)) => Err(EngineError::new_terraform_error_while_executing_destroy_pipeline(
|
||||
event_details.clone(),
|
||||
CommandError::new(msg, None),
|
||||
CommandError::new_from_safe_message(msg),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,8 +66,10 @@ impl FromStr for AwsInstancesType {
|
||||
"t3a.large" => Ok(AwsInstancesType::T3aLarge),
|
||||
"t3a.2xlarge" => Ok(AwsInstancesType::T3a2xlarge),
|
||||
_ => {
|
||||
let message = format!("`{}` instance type is not supported", s);
|
||||
return Err(CommandError::new(message.clone(), Some(message)));
|
||||
return Err(CommandError::new_from_safe_message(format!(
|
||||
"`{}` instance type is not supported",
|
||||
s
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,9 @@ impl Role {
|
||||
match role {
|
||||
Ok(_) => Ok(true),
|
||||
Err(e) => Err(CommandError::new(
|
||||
format!("Unable to know if {} exist on AWS account: {:?}", &self.role_name, e),
|
||||
Some(format!("Unable to know if {} exist on AWS account.", &self.role_name,)),
|
||||
format!("Unable to know if {} exist on AWS account.", &self.role_name,),
|
||||
Some(e.to_string()),
|
||||
true,
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -78,10 +79,10 @@ impl Role {
|
||||
return match created {
|
||||
Ok(_) => Ok(true),
|
||||
Err(e) => {
|
||||
let safe_message = format!("Unable to know if `{}` exist on AWS Account", &self.role_name);
|
||||
return Err(CommandError::new(
|
||||
format!("{}, error: {:?}", safe_message, e),
|
||||
Some(safe_message),
|
||||
format!("Unable to know if `{}` exist on AWS Account", &self.role_name),
|
||||
Some(e.to_string()),
|
||||
true,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -33,28 +33,26 @@ pub fn do_get_from_api(token: &str, api_type: DoApiType, url_api: String) -> Res
|
||||
let res = reqwest::blocking::Client::new().get(url_api).headers(headers).send();
|
||||
|
||||
match res {
|
||||
Ok(response) => {
|
||||
match response.status() {
|
||||
StatusCode::OK => Ok(response.text().expect("Cannot get response text")),
|
||||
StatusCode::UNAUTHORIZED => {
|
||||
let message_safe = format!(
|
||||
Ok(response) => match response.status() {
|
||||
StatusCode::OK => Ok(response.text().expect("Cannot get response text")),
|
||||
StatusCode::UNAUTHORIZED => {
|
||||
return Err(CommandError::new(
|
||||
format!(
|
||||
"Could not get {} information, ensure your DigitalOcean token is valid.",
|
||||
api_type
|
||||
);
|
||||
return Err(CommandError::new(
|
||||
format!("{}, response: {:?}", message_safe.to_string(), response),
|
||||
Some(message_safe.to_string()),
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
let message_safe = format!("Unknown status code received from Digital Ocean Kubernetes API while retrieving {} information.", api_type);
|
||||
return Err(CommandError::new(
|
||||
format!("{}, response: {:?}", message_safe.to_string(), response),
|
||||
Some(message_safe.to_string()),
|
||||
));
|
||||
}
|
||||
),
|
||||
Some(format!("response: {:?}", response)),
|
||||
true,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(CommandError::new(
|
||||
format!("Unknown status code received from Digital Ocean Kubernetes API while retrieving {} information.", api_type),
|
||||
Some(format!("Response: {:?}", response)),
|
||||
true,
|
||||
));
|
||||
}
|
||||
},
|
||||
Err(_) => Err(CommandError::new_from_safe_message(format!(
|
||||
"Unable to get a response from Digital Ocean {} API",
|
||||
api_type
|
||||
|
||||
@@ -25,10 +25,10 @@ pub fn get_doks_info_from_name(
|
||||
Ok(cluster_info)
|
||||
}
|
||||
Err(e) => {
|
||||
let safe_message = "Error while trying to deserialize json received from Digital Ocean DOKS API";
|
||||
return Err(CommandError::new(
|
||||
format!("{}, error: {}", safe_message.to_string(), e.to_string()),
|
||||
Some(safe_message.to_string()),
|
||||
"Error while trying to deserialize json received from Digital Ocean DOKS API".to_string(),
|
||||
Some(e.to_string()),
|
||||
true,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -49,10 +49,10 @@ fn get_doks_versions_from_api_output(json_content: &str) -> Result<Vec<Kubernete
|
||||
match res_doks_options {
|
||||
Ok(options) => Ok(options.options.versions),
|
||||
Err(e) => {
|
||||
let safe_message = "Error while trying to deserialize json received from Digital Ocean DOKS API";
|
||||
return Err(CommandError::new(
|
||||
format!("{}, error: {}", safe_message.to_string(), e.to_string()),
|
||||
Some(safe_message.to_string()),
|
||||
"Error while trying to deserialize json received from Digital Ocean DOKS API".to_string(),
|
||||
Some(e.to_string()),
|
||||
true,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,10 +121,11 @@ pub fn do_helm_charts(
|
||||
let content_file = match File::open(&qovery_terraform_config_file) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
let message_safe = "Can't deploy helm chart as Qovery terraform config file has not been rendered by Terraform. Are you running it in dry run mode?";
|
||||
let message_safe =
|
||||
return Err(CommandError::new(
|
||||
format!("{}, error: {:?}", message_safe.to_string(), e),
|
||||
Some(message_safe.to_string()),
|
||||
"Can't deploy helm chart as Qovery terraform config file has not been rendered by Terraform. Are you running it in dry run mode?".to_string(),
|
||||
Some(e.to_string()),
|
||||
true,
|
||||
));
|
||||
}
|
||||
};
|
||||
@@ -134,13 +135,13 @@ pub fn do_helm_charts(
|
||||
let qovery_terraform_config: DigitalOceanQoveryTerraformConfig = match serde_json::from_reader(reader) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
let message_safe = format!(
|
||||
"Error while parsing terraform config file {}",
|
||||
qovery_terraform_config_file
|
||||
);
|
||||
return Err(CommandError::new(
|
||||
format!("{}, error: {:?}", message_safe.to_string(), e),
|
||||
Some(message_safe.to_string()),
|
||||
format!(
|
||||
"Error while parsing terraform config file {}",
|
||||
qovery_terraform_config_file
|
||||
),
|
||||
Some(e.to_string()),
|
||||
true,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -71,8 +71,10 @@ impl FromStr for DoInstancesType {
|
||||
"s-6vcpu-16gb" => Ok(DoInstancesType::S6vcpu16gb),
|
||||
"s-8vcpu-32gb" => Ok(DoInstancesType::S8vcpu32gb),
|
||||
_ => {
|
||||
let message = format!("`{}` instance type is not supported", s);
|
||||
return Err(CommandError::new(message.clone(), Some(message)));
|
||||
return Err(CommandError::new_from_safe_message(format!(
|
||||
"`{}` instance type is not supported",
|
||||
s
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ pub struct CommandError {
|
||||
impl From<errors::CommandError> for CommandError {
|
||||
fn from(error: errors::CommandError) -> Self {
|
||||
CommandError {
|
||||
message: error.message_safe.unwrap_or("".to_string()),
|
||||
message_unsafe: error.message_raw,
|
||||
message: error.safe_message.unwrap_or("".to_string()),
|
||||
message_unsafe: error.full_details,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,34 +7,48 @@ use crate::error::{EngineError as LegacyEngineError, EngineErrorCause, EngineErr
|
||||
use crate::events::EventDetails;
|
||||
use url::Url;
|
||||
|
||||
pub enum CommandErrorMessageVerbosity {
|
||||
SafeOnly,
|
||||
FullDetails,
|
||||
}
|
||||
|
||||
/// CommandError: command error, mostly returned by third party tools.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CommandError {
|
||||
/// message: full error message, can contains unsafe text such as passwords and tokens.
|
||||
message_raw: String,
|
||||
/// message_safe: error message omitting displaying any protected data such as passwords and tokens.
|
||||
message_safe: Option<String>,
|
||||
/// safe_message: error message omitting displaying any protected data such as passwords and tokens.
|
||||
safe_message: String,
|
||||
/// full_details: full error message, can contains unsafe text such as passwords and tokens.
|
||||
full_details: Option<String>,
|
||||
}
|
||||
|
||||
impl CommandError {
|
||||
/// Returns CommandError message_raw. May contains unsafe text such as passwords and tokens.
|
||||
pub fn message_raw(&self) -> String {
|
||||
self.message_raw.to_string()
|
||||
/// Returns CommandError full_details. May contains unsafe text such as passwords and tokens.
|
||||
pub fn full_details(&self) -> Option<String> {
|
||||
self.full_details.clone()
|
||||
}
|
||||
|
||||
/// Returns CommandError message_safe omitting all unsafe text such as passwords and tokens.
|
||||
pub fn message_safe(&self) -> Option<String> {
|
||||
self.message_safe.clone()
|
||||
/// Returns CommandError safe_message omitting all unsafe text such as passwords and tokens.
|
||||
pub fn safe_message(&self) -> String {
|
||||
self.safe_message.to_string()
|
||||
}
|
||||
|
||||
/// Returns error all message (safe + unsafe).
|
||||
pub fn message(&self) -> String {
|
||||
// TODO(benjaminch): To be revamped, not sure how we should deal with safe and unsafe messages.
|
||||
if let Some(msg) = &self.message_safe {
|
||||
return format!("{} {}", msg, self.message_raw);
|
||||
/// Returns message for command error.
|
||||
///
|
||||
/// Arguments
|
||||
///
|
||||
/// * `message_verbosity`: Which verbosity is required for the message.
|
||||
pub fn message(&self, message_verbosity: CommandErrorMessageVerbosity) -> String {
|
||||
match message_verbosity {
|
||||
CommandErrorMessageVerbosity::SafeOnly => self.safe_message.to_string(),
|
||||
CommandErrorMessageVerbosity::FullDetails => match &self.full_details {
|
||||
None => self.safe_message.to_string(),
|
||||
Some(details) => format!(
|
||||
"{} / Full error details: {}",
|
||||
self.safe_message.to_string(),
|
||||
details.to_string()
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
self.message_raw.to_string()
|
||||
}
|
||||
|
||||
/// Creates a new CommandError from safe message. To be used when message is safe.
|
||||
@@ -43,10 +57,10 @@ impl CommandError {
|
||||
}
|
||||
|
||||
/// Creates a new CommandError having both a safe and an unsafe message.
|
||||
pub fn new(message_raw: String, message_safe: Option<String>) -> Self {
|
||||
pub fn new(safe_message: String, full_details: Option<String>, refactor: bool) -> Self {
|
||||
CommandError {
|
||||
message_raw,
|
||||
message_safe,
|
||||
full_details,
|
||||
safe_message,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +91,7 @@ impl CommandError {
|
||||
unsafe_message = format!("{}\nSTDERR {}", unsafe_message, txt);
|
||||
}
|
||||
|
||||
CommandError::new(unsafe_message, Some(message))
|
||||
CommandError::new(message, Some(unsafe_message), true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,9 +249,9 @@ impl EngineError {
|
||||
}
|
||||
|
||||
/// Returns proper error message.
|
||||
pub fn message(&self) -> String {
|
||||
pub fn message(&self, message_verbosity: CommandErrorMessageVerbosity) -> String {
|
||||
match &self.message {
|
||||
Some(msg) => msg.message(),
|
||||
Some(msg) => msg.message(message_verbosity),
|
||||
None => self.qovery_log_message.to_string(),
|
||||
}
|
||||
}
|
||||
@@ -261,7 +275,7 @@ impl EngineError {
|
||||
/// * `qovery_log_message`: Error log message targeting Qovery team for investigation / monitoring purposes.
|
||||
/// * `user_log_message`: Error log message targeting Qovery user, avoiding any extending pointless details.
|
||||
/// * `error_message`: Raw error message.
|
||||
/// * `raw_message_safe`: Error raw message such as command input / output where any unsafe data as been omitted (such as plain passwords / tokens).
|
||||
/// * `raw_safe_message`: Error raw message such as command input / output where any unsafe data as been omitted (such as plain passwords / tokens).
|
||||
/// * `link`: Link documenting the given error.
|
||||
/// * `hint_message`: hint message aiming to give an hint to the user. For example: "Happens when application port has been changed but application hasn't been restarted.".
|
||||
fn new(
|
||||
@@ -290,7 +304,7 @@ impl EngineError {
|
||||
EngineErrorCause::Internal,
|
||||
EngineErrorScope::from(self.event_details.transmitter()),
|
||||
self.event_details.execution_id().to_string(),
|
||||
Some(self.message()),
|
||||
Some(self.message(CommandErrorMessageVerbosity::FullDetails)), // Full details verbosity by default for legacy errors
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user