refactor: ease command error message safe / unsafe logic

This commit is contained in:
Benjamin Chastanier
2022-02-14 11:39:09 +01:00
parent e2271cb132
commit 10be11957d
10 changed files with 150 additions and 115 deletions

View File

@@ -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,
)),
}
}

View File

@@ -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),
)),
}
}

View File

@@ -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
)));
}
}
}

View File

@@ -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,
));
}
};

View File

@@ -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

View File

@@ -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,
));
}
}

View File

@@ -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,
));
}
};

View File

@@ -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
)));
}
}
}

View File

@@ -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,
}
}
}

View File

@@ -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
)
}