mirror of
https://github.com/jlengrand/engine.git
synced 2026-03-10 08:11:21 +00:00
committed by
GitHub
parent
5bb6e4a37d
commit
2a6760ff87
@@ -1,5 +0,0 @@
|
||||
pub mod mongodb;
|
||||
pub mod mysql;
|
||||
pub mod postgresql;
|
||||
pub mod redis;
|
||||
pub mod utilities;
|
||||
@@ -1,451 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::cloud_provider::aws::databases::utilities::aws_final_snapshot_name;
|
||||
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, ServiceVersionCheckResult, StatefulService, Terraform,
|
||||
};
|
||||
use crate::cloud_provider::utilities::{
|
||||
generate_supported_version, get_self_hosted_mongodb_version, get_supported_version_to_use, print_action,
|
||||
};
|
||||
use crate::cloud_provider::DeploymentTarget;
|
||||
use crate::cmd::helm::Timeout;
|
||||
use crate::cmd::kubectl;
|
||||
use crate::errors::{CommandError, EngineError};
|
||||
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter};
|
||||
use crate::io_models::DatabaseMode::MANAGED;
|
||||
use crate::io_models::{Context, Listen, Listener, Listeners};
|
||||
use crate::logger::Logger;
|
||||
use ::function_name::named;
|
||||
|
||||
pub struct MongoDbAws {
|
||||
context: Context,
|
||||
id: String,
|
||||
action: Action,
|
||||
name: String,
|
||||
version: String,
|
||||
fqdn: String,
|
||||
fqdn_id: String,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: String,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
}
|
||||
|
||||
impl MongoDbAws {
|
||||
pub fn new(
|
||||
context: Context,
|
||||
id: &str,
|
||||
action: Action,
|
||||
name: &str,
|
||||
version: &str,
|
||||
fqdn: &str,
|
||||
fqdn_id: &str,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: &str,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
) -> Self {
|
||||
MongoDbAws {
|
||||
context,
|
||||
action,
|
||||
id: id.to_string(),
|
||||
name: name.to_string(),
|
||||
version: version.to_string(),
|
||||
fqdn: fqdn.to_string(),
|
||||
fqdn_id: fqdn_id.to_string(),
|
||||
total_cpus,
|
||||
total_ram_in_mib,
|
||||
database_instance_type: database_instance_type.to_string(),
|
||||
options,
|
||||
listeners,
|
||||
logger,
|
||||
}
|
||||
}
|
||||
|
||||
fn matching_correct_version(
|
||||
&self,
|
||||
is_managed_services: bool,
|
||||
event_details: EventDetails,
|
||||
) -> Result<ServiceVersionCheckResult, EngineError> {
|
||||
check_service_version(
|
||||
get_mongodb_version(self.version(), is_managed_services),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
}
|
||||
|
||||
fn cloud_provider_name(&self) -> &str {
|
||||
"aws"
|
||||
}
|
||||
|
||||
fn struct_name(&self) -> &str {
|
||||
"mongodb"
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulService for MongoDbAws {
|
||||
fn as_stateful_service(&self) -> &dyn StatefulService {
|
||||
self
|
||||
}
|
||||
|
||||
fn is_managed_service(&self) -> bool {
|
||||
self.options.mode == MANAGED
|
||||
}
|
||||
}
|
||||
|
||||
impl Service for MongoDbAws {
|
||||
fn context(&self) -> &Context {
|
||||
&self.context
|
||||
}
|
||||
|
||||
fn service_type(&self) -> ServiceType {
|
||||
ServiceType::Database(DatabaseType::MongoDB(&self.options))
|
||||
}
|
||||
|
||||
fn id(&self) -> &str {
|
||||
self.id.as_str()
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
fn sanitized_name(&self) -> String {
|
||||
// https://docs.aws.amazon.com/documentdb/latest/developerguide/limits.html#limits-naming_constraints
|
||||
let prefix = "mongodb";
|
||||
let max_size = 60 - prefix.len(); // 63 (max DocumentDB) - 3 (k8s statefulset chars)
|
||||
let mut new_name = format!("{}{}", prefix, self.name().replace('_', "").replace('-', ""));
|
||||
if new_name.chars().count() > max_size {
|
||||
new_name = new_name[..max_size].to_string();
|
||||
}
|
||||
|
||||
new_name
|
||||
}
|
||||
|
||||
fn version(&self) -> String {
|
||||
self.version.clone()
|
||||
}
|
||||
|
||||
fn action(&self) -> &Action {
|
||||
&self.action
|
||||
}
|
||||
|
||||
fn private_port(&self) -> Option<u16> {
|
||||
Some(self.options.port)
|
||||
}
|
||||
|
||||
fn start_timeout(&self) -> Timeout<u32> {
|
||||
Timeout::Default
|
||||
}
|
||||
|
||||
fn total_cpus(&self) -> String {
|
||||
self.total_cpus.to_string()
|
||||
}
|
||||
|
||||
fn cpu_burst(&self) -> String {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn total_ram_in_mib(&self) -> u32 {
|
||||
self.total_ram_in_mib
|
||||
}
|
||||
|
||||
fn min_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn max_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn publicly_accessible(&self) -> bool {
|
||||
self.options.publicly_accessible
|
||||
}
|
||||
|
||||
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, target.kubernetes, target.environment);
|
||||
|
||||
// we need the kubernetes config file to store tfstates file in kube secrets
|
||||
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(
|
||||
environment.namespace(),
|
||||
kube_config_file_path.as_str(),
|
||||
kubernetes.cloud_provider().credentials_environment_variables(),
|
||||
);
|
||||
|
||||
context.insert("namespace", environment.namespace());
|
||||
|
||||
let version = self
|
||||
.matching_correct_version(self.is_managed_service(), event_details)?
|
||||
.matched_version()
|
||||
.to_string();
|
||||
context.insert("version", &version);
|
||||
|
||||
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
|
||||
context.insert(k, v);
|
||||
}
|
||||
|
||||
context.insert("kubernetes_cluster_id", kubernetes.id());
|
||||
context.insert("kubernetes_cluster_name", kubernetes.name());
|
||||
|
||||
context.insert("fqdn_id", self.fqdn_id.as_str());
|
||||
context.insert("fqdn", self.fqdn(target, &self.fqdn, self.is_managed_service()).as_str());
|
||||
context.insert("service_name", self.fqdn_id.as_str());
|
||||
context.insert("database_db_name", self.name.as_str());
|
||||
context.insert("database_login", self.options.login.as_str());
|
||||
context.insert("database_password", self.options.password.as_str());
|
||||
context.insert("database_port", &self.private_port());
|
||||
context.insert("database_disk_size_in_gib", &self.options.disk_size_in_gib);
|
||||
context.insert("database_instance_type", &self.database_instance_type);
|
||||
context.insert("database_disk_type", &self.options.database_disk_type);
|
||||
context.insert("encrypt_disk", &self.options.encrypt_disk);
|
||||
context.insert("database_ram_size_in_mib", &self.total_ram_in_mib);
|
||||
context.insert("database_total_cpus", &self.total_cpus);
|
||||
context.insert("database_fqdn", &self.options.host.as_str());
|
||||
context.insert("database_id", &self.id());
|
||||
context.insert("tfstate_suffix_name", &get_tfstate_suffix(self));
|
||||
context.insert("tfstate_name", &get_tfstate_name(self));
|
||||
|
||||
context.insert("publicly_accessible", &self.options.publicly_accessible);
|
||||
context.insert("skip_final_snapshot", &false);
|
||||
context.insert("final_snapshot_name", &aws_final_snapshot_name(self.id()));
|
||||
context.insert("delete_automated_backups", &self.context().is_test_cluster());
|
||||
if self.context.resource_expiration_in_seconds().is_some() {
|
||||
context.insert("resource_expiration_in_seconds", &self.context.resource_expiration_in_seconds())
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
fn logger(&self) -> &dyn Logger {
|
||||
&*self.logger
|
||||
}
|
||||
|
||||
fn selector(&self) -> Option<String> {
|
||||
Some(format!("app={}", self.sanitized_name()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Database for MongoDbAws {}
|
||||
|
||||
impl ToTransmitter for MongoDbAws {
|
||||
fn to_transmitter(&self) -> Transmitter {
|
||||
Transmitter::Database(self.id().to_string(), self.service_type().to_string(), self.name().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Helm for MongoDbAws {
|
||||
fn helm_selector(&self) -> Option<String> {
|
||||
self.selector()
|
||||
}
|
||||
|
||||
fn helm_release_name(&self) -> String {
|
||||
crate::string::cut(format!("mongodb-{}", self.id()), 50)
|
||||
}
|
||||
|
||||
fn helm_chart_dir(&self) -> String {
|
||||
format!("{}/common/services/mongodb", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_values_dir(&self) -> String {
|
||||
format!("{}/aws/chart_values/mongodb", self.context.lib_root_dir()) // FIXME replace `chart_values` by `charts_values`
|
||||
}
|
||||
|
||||
fn helm_chart_external_name_service_dir(&self) -> String {
|
||||
format!("{}/common/charts/external-name-svc", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Terraform for MongoDbAws {
|
||||
fn terraform_common_resource_dir_path(&self) -> String {
|
||||
format!("{}/aws/services/common", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn terraform_resource_dir_path(&self) -> String {
|
||||
format!("{}/aws/services/mongodb", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Create for MongoDbAws {
|
||||
#[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_stateful_service(target, self, event_details.clone(), &*self.logger)
|
||||
})
|
||||
}
|
||||
|
||||
fn on_create_check(&self) -> Result<(), EngineError> {
|
||||
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
|
||||
self.check_domains(self.listeners.clone(), vec![self.fqdn.as_str()], event_details, self.logger())
|
||||
}
|
||||
|
||||
#[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 MongoDbAws {
|
||||
#[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, || {
|
||||
scale_down_database(target, self, 0)
|
||||
})
|
||||
}
|
||||
|
||||
fn on_pause_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 MongoDbAws {
|
||||
#[named]
|
||||
fn on_delete(&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.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())
|
||||
})
|
||||
}
|
||||
|
||||
fn on_delete_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 MongoDbAws {
|
||||
fn listeners(&self) -> &Listeners {
|
||||
&self.listeners
|
||||
}
|
||||
|
||||
fn add_listener(&mut self, listener: Listener) {
|
||||
self.listeners.push(listener);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mongodb_version(requested_version: String, is_managed_service: bool) -> Result<String, CommandError> {
|
||||
if is_managed_service {
|
||||
get_managed_mongodb_version(requested_version)
|
||||
} else {
|
||||
get_self_hosted_mongodb_version(requested_version)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_managed_mongodb_version(requested_version: String) -> Result<String, CommandError> {
|
||||
let mut supported_mongodb_versions = HashMap::new();
|
||||
|
||||
// v3.6.0
|
||||
let mongo_version = generate_supported_version(3, 6, 6, Some(0), Some(0), None);
|
||||
supported_mongodb_versions.extend(mongo_version);
|
||||
|
||||
// v4.0.0
|
||||
let mongo_version = generate_supported_version(4, 0, 0, Some(0), Some(0), None);
|
||||
supported_mongodb_versions.extend(mongo_version);
|
||||
|
||||
get_supported_version_to_use("DocumentDB", supported_mongodb_versions, requested_version)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_mongodb {
|
||||
use crate::cloud_provider::aws::databases::mongodb::get_mongodb_version;
|
||||
use crate::errors::ErrorMessageVerbosity;
|
||||
|
||||
#[test]
|
||||
fn check_mongodb_version() {
|
||||
// managed version
|
||||
assert_eq!(get_mongodb_version("4".to_string(), true).unwrap(), "4.0.0");
|
||||
assert_eq!(get_mongodb_version("4.0".to_string(), true).unwrap(), "4.0.0");
|
||||
assert!(get_mongodb_version("4.4".to_string(), true)
|
||||
.unwrap_err()
|
||||
.message(ErrorMessageVerbosity::FullDetails)
|
||||
.contains("DocumentDB 4.4 version is not supported"));
|
||||
// self-hosted version
|
||||
assert_eq!(get_mongodb_version("4".to_string(), false).unwrap(), "4.4.4");
|
||||
assert_eq!(get_mongodb_version("4.2".to_string(), false).unwrap(), "4.2.12");
|
||||
assert!(get_mongodb_version("3.4".to_string(), false)
|
||||
.unwrap_err()
|
||||
.message(ErrorMessageVerbosity::FullDetails)
|
||||
.contains("MongoDB 3.4 version is not supported"));
|
||||
}
|
||||
}
|
||||
@@ -1,473 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use tera::Context as TeraContext;
|
||||
|
||||
use crate::cloud_provider::aws::databases::utilities::{aws_final_snapshot_name, get_parameter_group_from_version};
|
||||
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, ServiceVersionCheckResult, StatefulService, Terraform,
|
||||
};
|
||||
use crate::cloud_provider::utilities::{
|
||||
generate_supported_version, get_self_hosted_mysql_version, get_supported_version_to_use, managed_db_name_sanitizer,
|
||||
print_action,
|
||||
};
|
||||
use crate::cloud_provider::DeploymentTarget;
|
||||
use crate::cmd::helm::Timeout;
|
||||
use crate::cmd::kubectl;
|
||||
use crate::errors::{CommandError, EngineError};
|
||||
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter};
|
||||
use crate::io_models::DatabaseMode::MANAGED;
|
||||
use crate::io_models::{Context, DatabaseKind, Listen, Listener, Listeners};
|
||||
use crate::logger::Logger;
|
||||
use ::function_name::named;
|
||||
|
||||
pub struct MySQLAws {
|
||||
context: Context,
|
||||
id: String,
|
||||
action: Action,
|
||||
name: String,
|
||||
version: String,
|
||||
fqdn: String,
|
||||
fqdn_id: String,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: String,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
}
|
||||
|
||||
impl MySQLAws {
|
||||
pub fn new(
|
||||
context: Context,
|
||||
id: &str,
|
||||
action: Action,
|
||||
name: &str,
|
||||
version: &str,
|
||||
fqdn: &str,
|
||||
fqdn_id: &str,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: &str,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
) -> Self {
|
||||
Self {
|
||||
context,
|
||||
action,
|
||||
id: id.to_string(),
|
||||
name: name.to_string(),
|
||||
version: version.to_string(),
|
||||
fqdn: fqdn.to_string(),
|
||||
fqdn_id: fqdn_id.to_string(),
|
||||
total_cpus,
|
||||
total_ram_in_mib,
|
||||
database_instance_type: database_instance_type.to_string(),
|
||||
options,
|
||||
listeners,
|
||||
logger,
|
||||
}
|
||||
}
|
||||
|
||||
fn matching_correct_version(
|
||||
&self,
|
||||
is_managed_services: bool,
|
||||
event_details: EventDetails,
|
||||
) -> Result<ServiceVersionCheckResult, EngineError> {
|
||||
check_service_version(
|
||||
get_mysql_version(self.version(), is_managed_services),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
}
|
||||
|
||||
fn cloud_provider_name(&self) -> &str {
|
||||
"aws"
|
||||
}
|
||||
|
||||
fn struct_name(&self) -> &str {
|
||||
"mysql"
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulService for MySQLAws {
|
||||
fn as_stateful_service(&self) -> &dyn StatefulService {
|
||||
self
|
||||
}
|
||||
|
||||
fn is_managed_service(&self) -> bool {
|
||||
self.options.mode == MANAGED
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTransmitter for MySQLAws {
|
||||
fn to_transmitter(&self) -> Transmitter {
|
||||
Transmitter::Database(self.id().to_string(), self.service_type().to_string(), self.name().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Service for MySQLAws {
|
||||
fn context(&self) -> &Context {
|
||||
&self.context
|
||||
}
|
||||
|
||||
fn service_type(&self) -> ServiceType {
|
||||
ServiceType::Database(DatabaseType::MySQL(&self.options))
|
||||
}
|
||||
|
||||
fn id(&self) -> &str {
|
||||
self.id.as_str()
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
fn sanitized_name(&self) -> String {
|
||||
// https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Limits.html#RDS_Limits.Constraints
|
||||
let prefix = "mysql";
|
||||
let max_size = 63 - 3; // max RDS - k8s statefulset chars
|
||||
managed_db_name_sanitizer(max_size, prefix, self.name())
|
||||
}
|
||||
|
||||
fn version(&self) -> String {
|
||||
self.version.clone()
|
||||
}
|
||||
|
||||
fn action(&self) -> &Action {
|
||||
&self.action
|
||||
}
|
||||
|
||||
fn private_port(&self) -> Option<u16> {
|
||||
Some(self.options.port)
|
||||
}
|
||||
|
||||
fn start_timeout(&self) -> Timeout<u32> {
|
||||
Timeout::Default
|
||||
}
|
||||
|
||||
fn total_cpus(&self) -> String {
|
||||
self.total_cpus.to_string()
|
||||
}
|
||||
|
||||
fn cpu_burst(&self) -> String {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn total_ram_in_mib(&self) -> u32 {
|
||||
self.total_ram_in_mib
|
||||
}
|
||||
|
||||
fn min_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn max_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn publicly_accessible(&self) -> bool {
|
||||
self.options.publicly_accessible
|
||||
}
|
||||
|
||||
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 = kubernetes.get_kubeconfig_file_path()?;
|
||||
context.insert("kubeconfig_path", &kube_config_file_path);
|
||||
|
||||
kubectl::kubectl_exec_create_namespace_without_labels(
|
||||
environment.namespace(),
|
||||
kube_config_file_path.as_str(),
|
||||
kubernetes.cloud_provider().credentials_environment_variables(),
|
||||
);
|
||||
|
||||
context.insert("namespace", environment.namespace());
|
||||
|
||||
let version = &self.matching_correct_version(self.is_managed_service(), event_details.clone())?;
|
||||
context.insert("version", &version.matched_version().to_string());
|
||||
|
||||
if self.is_managed_service() {
|
||||
let parameter_group_family =
|
||||
match get_parameter_group_from_version(version.matched_version(), DatabaseKind::Mysql) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return Err(EngineError::new_terraform_unsupported_context_parameter_value(
|
||||
event_details,
|
||||
"MySQL".to_string(),
|
||||
"parameter_group_family".to_string(),
|
||||
version.matched_version().to_string(),
|
||||
Some(e),
|
||||
))
|
||||
}
|
||||
};
|
||||
context.insert("parameter_group_family", ¶meter_group_family);
|
||||
};
|
||||
|
||||
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
|
||||
context.insert(k, v);
|
||||
}
|
||||
|
||||
context.insert("kubernetes_cluster_id", kubernetes.id());
|
||||
context.insert("kubernetes_cluster_name", kubernetes.name());
|
||||
|
||||
context.insert("fqdn_id", self.fqdn_id.as_str());
|
||||
context.insert("fqdn", self.fqdn(target, &self.fqdn, self.is_managed_service()).as_str());
|
||||
context.insert("service_name", self.fqdn_id.as_str());
|
||||
context.insert("database_login", self.options.login.as_str());
|
||||
context.insert("database_password", self.options.password.as_str());
|
||||
context.insert("database_port", &self.private_port());
|
||||
context.insert("database_disk_size_in_gib", &self.options.disk_size_in_gib);
|
||||
context.insert("database_instance_type", &self.database_instance_type);
|
||||
context.insert("database_disk_type", &self.options.database_disk_type);
|
||||
context.insert("encrypt_disk", &self.options.encrypt_disk);
|
||||
context.insert("database_name", &self.sanitized_name());
|
||||
context.insert("database_ram_size_in_mib", &self.total_ram_in_mib);
|
||||
context.insert("database_total_cpus", &self.total_cpus);
|
||||
context.insert("database_fqdn", &self.options.host.as_str());
|
||||
context.insert("database_id", &self.id());
|
||||
context.insert("tfstate_suffix_name", &get_tfstate_suffix(self));
|
||||
context.insert("tfstate_name", &get_tfstate_name(self));
|
||||
|
||||
context.insert("skip_final_snapshot", &false);
|
||||
context.insert("final_snapshot_name", &aws_final_snapshot_name(self.id()));
|
||||
context.insert("delete_automated_backups", &self.context().is_test_cluster());
|
||||
context.insert("publicly_accessible", &self.options.publicly_accessible);
|
||||
if self.context.resource_expiration_in_seconds().is_some() {
|
||||
context.insert("resource_expiration_in_seconds", &self.context.resource_expiration_in_seconds())
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
fn logger(&self) -> &dyn Logger {
|
||||
&*self.logger
|
||||
}
|
||||
|
||||
fn selector(&self) -> Option<String> {
|
||||
Some(format!("app={}", self.sanitized_name()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Database for MySQLAws {}
|
||||
|
||||
impl Helm for MySQLAws {
|
||||
fn helm_selector(&self) -> Option<String> {
|
||||
self.selector()
|
||||
}
|
||||
|
||||
fn helm_release_name(&self) -> String {
|
||||
crate::string::cut(format!("mysql-{}", self.id()), 50)
|
||||
}
|
||||
|
||||
fn helm_chart_dir(&self) -> String {
|
||||
format!("{}/common/services/mysql", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_values_dir(&self) -> String {
|
||||
format!("{}/aws/chart_values/mysql", self.context.lib_root_dir()) // FIXME replace `chart_values` by `charts_values`
|
||||
}
|
||||
|
||||
fn helm_chart_external_name_service_dir(&self) -> String {
|
||||
format!("{}/common/charts/external-name-svc", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Terraform for MySQLAws {
|
||||
fn terraform_common_resource_dir_path(&self) -> String {
|
||||
format!("{}/aws/services/common", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn terraform_resource_dir_path(&self) -> String {
|
||||
format!("{}/aws/services/mysql", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Create for MySQLAws {
|
||||
#[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_stateful_service(target, self, event_details.clone(), self.logger())
|
||||
})
|
||||
}
|
||||
|
||||
fn on_create_check(&self) -> Result<(), EngineError> {
|
||||
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
|
||||
self.check_domains(self.listeners.clone(), vec![self.fqdn.as_str()], event_details, self.logger())
|
||||
}
|
||||
|
||||
#[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 MySQLAws {
|
||||
#[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, || {
|
||||
scale_down_database(target, self, 0)
|
||||
})
|
||||
}
|
||||
|
||||
fn on_pause_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 MySQLAws {
|
||||
#[named]
|
||||
fn on_delete(&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.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())
|
||||
})
|
||||
}
|
||||
|
||||
fn on_delete_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 MySQLAws {
|
||||
fn listeners(&self) -> &Listeners {
|
||||
&self.listeners
|
||||
}
|
||||
|
||||
fn add_listener(&mut self, listener: Listener) {
|
||||
self.listeners.push(listener);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mysql_version(requested_version: String, is_managed_service: bool) -> Result<String, CommandError> {
|
||||
if is_managed_service {
|
||||
get_managed_mysql_version(requested_version)
|
||||
} else {
|
||||
get_self_hosted_mysql_version(requested_version)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_managed_mysql_version(requested_version: String) -> Result<String, CommandError> {
|
||||
let mut supported_mysql_versions = HashMap::new();
|
||||
// https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_MySQL.html#MySQL.Concepts.VersionMgmt
|
||||
|
||||
// v5.7
|
||||
let mut v57 = generate_supported_version(5, 7, 7, Some(16), Some(34), None);
|
||||
v57.remove("5.7.32");
|
||||
v57.remove("5.7.29");
|
||||
v57.remove("5.7.27");
|
||||
v57.remove("5.7.20");
|
||||
v57.remove("5.7.18");
|
||||
supported_mysql_versions.extend(v57);
|
||||
|
||||
// v8
|
||||
let mut v8 = generate_supported_version(8, 0, 0, Some(11), Some(26), None);
|
||||
v8.remove("8.0.24");
|
||||
v8.remove("8.0.22");
|
||||
v8.remove("8.0.18");
|
||||
v8.remove("8.0.14");
|
||||
v8.remove("8.0.12");
|
||||
supported_mysql_versions.extend(v8);
|
||||
|
||||
get_supported_version_to_use("RDS MySQL", supported_mysql_versions, requested_version)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_mysql {
|
||||
use crate::cloud_provider::aws::databases::mysql::get_mysql_version;
|
||||
use crate::errors::ErrorMessageVerbosity;
|
||||
|
||||
#[test]
|
||||
fn check_mysql_version() {
|
||||
// managed version
|
||||
assert_eq!(get_mysql_version("8".to_string(), true).unwrap(), "8.0.26");
|
||||
assert_eq!(get_mysql_version("8.0".to_string(), true).unwrap(), "8.0.26");
|
||||
assert_eq!(get_mysql_version("8.0.16".to_string(), true).unwrap(), "8.0.16");
|
||||
assert!(get_mysql_version("8.0.18".to_string(), true)
|
||||
.unwrap_err()
|
||||
.message(ErrorMessageVerbosity::FullDetails)
|
||||
.contains("RDS MySQL 8.0.18 version is not supported"));
|
||||
// self-hosted version
|
||||
assert_eq!(get_mysql_version("5".to_string(), false).unwrap(), "5.7.34");
|
||||
assert_eq!(get_mysql_version("5.7".to_string(), false).unwrap(), "5.7.34");
|
||||
assert_eq!(get_mysql_version("5.7.31".to_string(), false).unwrap(), "5.7.31");
|
||||
assert!(get_mysql_version("1.0".to_string(), false)
|
||||
.unwrap_err()
|
||||
.message(ErrorMessageVerbosity::FullDetails)
|
||||
.contains("MySQL 1.0 version is not supported"));
|
||||
}
|
||||
}
|
||||
@@ -1,468 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::cloud_provider::aws::databases::utilities::aws_final_snapshot_name;
|
||||
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, ServiceVersionCheckResult, StatefulService, Terraform,
|
||||
};
|
||||
use crate::cloud_provider::utilities::{
|
||||
generate_supported_version, get_self_hosted_postgres_version, get_supported_version_to_use,
|
||||
managed_db_name_sanitizer, print_action,
|
||||
};
|
||||
use crate::cloud_provider::DeploymentTarget;
|
||||
use crate::cmd::helm::Timeout;
|
||||
use crate::cmd::kubectl;
|
||||
use crate::errors::{CommandError, EngineError};
|
||||
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter};
|
||||
use crate::io_models::DatabaseMode::MANAGED;
|
||||
use crate::io_models::{Context, Listen, Listener, Listeners};
|
||||
use crate::logger::Logger;
|
||||
use ::function_name::named;
|
||||
|
||||
pub struct PostgreSQLAws {
|
||||
context: Context,
|
||||
id: String,
|
||||
action: Action,
|
||||
name: String,
|
||||
version: String,
|
||||
fqdn: String,
|
||||
fqdn_id: String,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: String,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
}
|
||||
|
||||
impl PostgreSQLAws {
|
||||
pub fn new(
|
||||
context: Context,
|
||||
id: &str,
|
||||
action: Action,
|
||||
name: &str,
|
||||
version: &str,
|
||||
fqdn: &str,
|
||||
fqdn_id: &str,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: &str,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
) -> Self {
|
||||
PostgreSQLAws {
|
||||
context,
|
||||
action,
|
||||
id: id.to_string(),
|
||||
name: name.to_string(),
|
||||
version: version.to_string(),
|
||||
fqdn: fqdn.to_string(),
|
||||
fqdn_id: fqdn_id.to_string(),
|
||||
total_cpus,
|
||||
total_ram_in_mib,
|
||||
database_instance_type: database_instance_type.to_string(),
|
||||
options,
|
||||
listeners,
|
||||
logger,
|
||||
}
|
||||
}
|
||||
|
||||
fn matching_correct_version(
|
||||
&self,
|
||||
is_managed_services: bool,
|
||||
event_details: EventDetails,
|
||||
) -> Result<ServiceVersionCheckResult, EngineError> {
|
||||
check_service_version(
|
||||
get_postgres_version(self.version(), is_managed_services),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
}
|
||||
|
||||
fn cloud_provider_name(&self) -> &str {
|
||||
"aws"
|
||||
}
|
||||
|
||||
fn struct_name(&self) -> &str {
|
||||
"postgresql"
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulService for PostgreSQLAws {
|
||||
fn as_stateful_service(&self) -> &dyn StatefulService {
|
||||
self
|
||||
}
|
||||
|
||||
fn is_managed_service(&self) -> bool {
|
||||
self.options.mode == MANAGED
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTransmitter for PostgreSQLAws {
|
||||
fn to_transmitter(&self) -> Transmitter {
|
||||
Transmitter::Database(self.id().to_string(), self.service_type().to_string(), self.name().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Service for PostgreSQLAws {
|
||||
fn context(&self) -> &Context {
|
||||
&self.context
|
||||
}
|
||||
|
||||
fn service_type(&self) -> ServiceType {
|
||||
ServiceType::Database(DatabaseType::PostgreSQL(&self.options))
|
||||
}
|
||||
|
||||
fn id(&self) -> &str {
|
||||
self.id.as_str()
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
fn sanitized_name(&self) -> String {
|
||||
// https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Limits.html#RDS_Limits.Constraints
|
||||
let prefix = "postgresql";
|
||||
let max_size = 63 - 3; // max RDS - k8s statefulset chars
|
||||
managed_db_name_sanitizer(max_size, prefix, self.name())
|
||||
}
|
||||
|
||||
fn version(&self) -> String {
|
||||
self.version.clone()
|
||||
}
|
||||
|
||||
fn action(&self) -> &Action {
|
||||
&self.action
|
||||
}
|
||||
|
||||
fn private_port(&self) -> Option<u16> {
|
||||
Some(self.options.port)
|
||||
}
|
||||
|
||||
fn start_timeout(&self) -> Timeout<u32> {
|
||||
Timeout::Default
|
||||
}
|
||||
|
||||
fn total_cpus(&self) -> String {
|
||||
self.total_cpus.to_string()
|
||||
}
|
||||
|
||||
fn cpu_burst(&self) -> String {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn total_ram_in_mib(&self) -> u32 {
|
||||
self.total_ram_in_mib
|
||||
}
|
||||
|
||||
fn min_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn max_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn publicly_accessible(&self) -> bool {
|
||||
self.options.publicly_accessible
|
||||
}
|
||||
|
||||
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 = kubernetes.get_kubeconfig_file_path()?;
|
||||
context.insert("kubeconfig_path", &kube_config_file_path);
|
||||
|
||||
kubectl::kubectl_exec_create_namespace_without_labels(
|
||||
environment.namespace(),
|
||||
kube_config_file_path.as_str(),
|
||||
kubernetes.cloud_provider().credentials_environment_variables(),
|
||||
);
|
||||
|
||||
context.insert("namespace", environment.namespace());
|
||||
|
||||
let version = self
|
||||
.matching_correct_version(self.is_managed_service(), event_details)?
|
||||
.matched_version()
|
||||
.to_string();
|
||||
context.insert("version", &version);
|
||||
|
||||
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
|
||||
context.insert(k, v);
|
||||
}
|
||||
|
||||
context.insert("kubernetes_cluster_id", kubernetes.id());
|
||||
context.insert("kubernetes_cluster_name", kubernetes.name());
|
||||
|
||||
context.insert("fqdn_id", self.fqdn_id.as_str());
|
||||
context.insert("fqdn", self.fqdn(target, &self.fqdn, self.is_managed_service()).as_str());
|
||||
context.insert("service_name", self.fqdn_id.as_str());
|
||||
context.insert("database_name", self.sanitized_name().as_str());
|
||||
context.insert("database_db_name", self.name());
|
||||
context.insert("database_login", self.options.login.as_str());
|
||||
context.insert("database_password", self.options.password.as_str());
|
||||
context.insert("database_port", &self.private_port());
|
||||
context.insert("database_disk_size_in_gib", &self.options.disk_size_in_gib);
|
||||
context.insert("database_instance_type", &self.database_instance_type);
|
||||
context.insert("database_disk_type", &self.options.database_disk_type);
|
||||
context.insert("encrypt_disk", &self.options.encrypt_disk);
|
||||
context.insert("database_ram_size_in_mib", &self.total_ram_in_mib);
|
||||
context.insert("database_total_cpus", &self.total_cpus);
|
||||
context.insert("database_fqdn", &self.options.host.as_str());
|
||||
context.insert("database_id", &self.id());
|
||||
context.insert("tfstate_suffix_name", &get_tfstate_suffix(self));
|
||||
context.insert("tfstate_name", &get_tfstate_name(self));
|
||||
|
||||
context.insert("skip_final_snapshot", &false);
|
||||
context.insert("final_snapshot_name", &aws_final_snapshot_name(self.id()));
|
||||
context.insert("delete_automated_backups", &self.context().is_test_cluster());
|
||||
|
||||
context.insert("publicly_accessible", &self.options.publicly_accessible);
|
||||
if self.context.resource_expiration_in_seconds().is_some() {
|
||||
context.insert("resource_expiration_in_seconds", &self.context.resource_expiration_in_seconds())
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
fn logger(&self) -> &dyn Logger {
|
||||
&*self.logger
|
||||
}
|
||||
|
||||
fn selector(&self) -> Option<String> {
|
||||
Some(format!("app={}", self.sanitized_name()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Database for PostgreSQLAws {}
|
||||
|
||||
impl Helm for PostgreSQLAws {
|
||||
fn helm_selector(&self) -> Option<String> {
|
||||
self.selector()
|
||||
}
|
||||
|
||||
fn helm_release_name(&self) -> String {
|
||||
crate::string::cut(format!("postgresql-{}", self.id()), 50)
|
||||
}
|
||||
|
||||
fn helm_chart_dir(&self) -> String {
|
||||
format!("{}/common/services/postgresql", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_values_dir(&self) -> String {
|
||||
format!("{}/aws/chart_values/postgresql", self.context.lib_root_dir()) // FIXME replace `chart_values` by `charts_values`
|
||||
}
|
||||
|
||||
fn helm_chart_external_name_service_dir(&self) -> String {
|
||||
format!("{}/common/charts/external-name-svc", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Terraform for PostgreSQLAws {
|
||||
fn terraform_common_resource_dir_path(&self) -> String {
|
||||
format!("{}/aws/services/common", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn terraform_resource_dir_path(&self) -> String {
|
||||
format!("{}/aws/services/postgresql", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Create for PostgreSQLAws {
|
||||
#[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_stateful_service(target, self, event_details.clone(), self.logger())
|
||||
})
|
||||
}
|
||||
|
||||
fn on_create_check(&self) -> Result<(), EngineError> {
|
||||
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
|
||||
self.check_domains(self.listeners.clone(), vec![self.fqdn.as_str()], event_details, self.logger())
|
||||
}
|
||||
|
||||
#[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 PostgreSQLAws {
|
||||
#[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, || {
|
||||
scale_down_database(target, self, 0)
|
||||
})
|
||||
}
|
||||
|
||||
fn on_pause_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 PostgreSQLAws {
|
||||
#[named]
|
||||
fn on_delete(&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.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())
|
||||
})
|
||||
}
|
||||
|
||||
fn on_delete_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 PostgreSQLAws {
|
||||
fn listeners(&self) -> &Listeners {
|
||||
&self.listeners
|
||||
}
|
||||
|
||||
fn add_listener(&mut self, listener: Listener) {
|
||||
self.listeners.push(listener);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_postgres_version(requested_version: String, is_managed_service: bool) -> Result<String, CommandError> {
|
||||
if is_managed_service {
|
||||
get_managed_postgres_version(requested_version)
|
||||
} else {
|
||||
get_self_hosted_postgres_version(requested_version)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_managed_postgres_version(requested_version: String) -> Result<String, CommandError> {
|
||||
let mut supported_postgres_versions = HashMap::new();
|
||||
|
||||
// https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_PostgreSQL.html#PostgreSQL.Concepts
|
||||
|
||||
// v10
|
||||
let mut v10 = generate_supported_version(10, 1, 18, None, None, None);
|
||||
v10.remove("10.2"); // non supported version by AWS
|
||||
v10.remove("10.8"); // non supported version by AWS
|
||||
supported_postgres_versions.extend(v10);
|
||||
|
||||
// v11
|
||||
let mut v11 = generate_supported_version(11, 1, 13, None, None, None);
|
||||
v11.remove("11.3"); // non supported version by AWS
|
||||
supported_postgres_versions.extend(v11);
|
||||
|
||||
// v12
|
||||
let v12 = generate_supported_version(12, 2, 8, None, None, None);
|
||||
supported_postgres_versions.extend(v12);
|
||||
|
||||
// v13
|
||||
let v13 = generate_supported_version(13, 1, 4, None, None, None);
|
||||
supported_postgres_versions.extend(v13);
|
||||
|
||||
get_supported_version_to_use("Postgresql", supported_postgres_versions, requested_version)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_postgres {
|
||||
use crate::cloud_provider::aws::databases::postgresql::get_postgres_version;
|
||||
use crate::errors::ErrorMessageVerbosity;
|
||||
|
||||
#[test]
|
||||
fn check_postgres_version() {
|
||||
// managed version
|
||||
assert_eq!(get_postgres_version("12".to_string(), true).unwrap(), "12.8");
|
||||
assert_eq!(get_postgres_version("12.3".to_string(), true).unwrap(), "12.3");
|
||||
assert!(get_postgres_version("12.3.0".to_string(), true)
|
||||
.unwrap_err()
|
||||
.message(ErrorMessageVerbosity::FullDetails)
|
||||
.contains("Postgresql 12.3.0 version is not supported"));
|
||||
assert!(get_postgres_version("11.3".to_string(), true)
|
||||
.unwrap_err()
|
||||
.message(ErrorMessageVerbosity::FullDetails)
|
||||
.contains("Postgresql 11.3 version is not supported"));
|
||||
// self-hosted version
|
||||
assert_eq!(get_postgres_version("12".to_string(), false).unwrap(), "12.8.0");
|
||||
assert_eq!(get_postgres_version("12.8".to_string(), false).unwrap(), "12.8.0");
|
||||
assert_eq!(get_postgres_version("12.3.0".to_string(), false).unwrap(), "12.3.0");
|
||||
assert!(get_postgres_version("1.0".to_string(), false)
|
||||
.unwrap_err()
|
||||
.message(ErrorMessageVerbosity::FullDetails)
|
||||
.contains("Postgresql 1.0 version is not supported"));
|
||||
}
|
||||
}
|
||||
@@ -1,461 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::cloud_provider::aws::databases::utilities::aws_final_snapshot_name;
|
||||
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, ServiceVersionCheckResult, StatefulService, Terraform,
|
||||
};
|
||||
use crate::cloud_provider::utilities::{get_self_hosted_redis_version, get_supported_version_to_use, print_action};
|
||||
use crate::cloud_provider::DeploymentTarget;
|
||||
use crate::cmd::helm::Timeout;
|
||||
use crate::cmd::kubectl;
|
||||
use crate::errors::{CommandError, EngineError};
|
||||
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter};
|
||||
use crate::io_models::DatabaseMode::MANAGED;
|
||||
use crate::io_models::{Context, Listen, Listener, Listeners};
|
||||
use crate::logger::Logger;
|
||||
use ::function_name::named;
|
||||
|
||||
pub struct RedisAws {
|
||||
context: Context,
|
||||
id: String,
|
||||
action: Action,
|
||||
name: String,
|
||||
version: String,
|
||||
fqdn: String,
|
||||
fqdn_id: String,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: String,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
}
|
||||
|
||||
impl RedisAws {
|
||||
pub fn new(
|
||||
context: Context,
|
||||
id: &str,
|
||||
action: Action,
|
||||
name: &str,
|
||||
version: &str,
|
||||
fqdn: &str,
|
||||
fqdn_id: &str,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: &str,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
) -> Self {
|
||||
Self {
|
||||
context,
|
||||
action,
|
||||
id: id.to_string(),
|
||||
name: name.to_string(),
|
||||
version: version.to_string(),
|
||||
fqdn: fqdn.to_string(),
|
||||
fqdn_id: fqdn_id.to_string(),
|
||||
total_cpus,
|
||||
total_ram_in_mib,
|
||||
database_instance_type: database_instance_type.to_string(),
|
||||
options,
|
||||
listeners,
|
||||
logger,
|
||||
}
|
||||
}
|
||||
|
||||
fn matching_correct_version(
|
||||
&self,
|
||||
is_managed_services: bool,
|
||||
event_details: EventDetails,
|
||||
) -> Result<ServiceVersionCheckResult, EngineError> {
|
||||
check_service_version(
|
||||
get_redis_version(self.version(), is_managed_services),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
}
|
||||
|
||||
fn cloud_provider_name(&self) -> &str {
|
||||
"aws"
|
||||
}
|
||||
|
||||
fn struct_name(&self) -> &str {
|
||||
"redis"
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulService for RedisAws {
|
||||
fn as_stateful_service(&self) -> &dyn StatefulService {
|
||||
self
|
||||
}
|
||||
|
||||
fn is_managed_service(&self) -> bool {
|
||||
self.options.mode == MANAGED
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTransmitter for RedisAws {
|
||||
fn to_transmitter(&self) -> Transmitter {
|
||||
Transmitter::Database(self.id().to_string(), self.service_type().to_string(), self.name().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Service for RedisAws {
|
||||
fn context(&self) -> &Context {
|
||||
&self.context
|
||||
}
|
||||
|
||||
fn service_type(&self) -> ServiceType {
|
||||
ServiceType::Database(DatabaseType::Redis(&self.options))
|
||||
}
|
||||
|
||||
fn id(&self) -> &str {
|
||||
self.id.as_str()
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
fn sanitized_name(&self) -> String {
|
||||
// https://aws.amazon.com/about-aws/whats-new/2019/08/elasticache_supports_50_chars_cluster_name
|
||||
let prefix = "redis";
|
||||
let max_size = 47 - prefix.len(); // 50 (max Elasticache ) - 3 (k8s statefulset chars)
|
||||
let mut new_name = self.name().replace('_', "").replace('-', "");
|
||||
|
||||
if new_name.chars().count() > max_size {
|
||||
new_name = new_name[..max_size].to_string();
|
||||
}
|
||||
|
||||
format!("{}{}", prefix, new_name)
|
||||
}
|
||||
|
||||
fn version(&self) -> String {
|
||||
self.version.clone()
|
||||
}
|
||||
|
||||
fn action(&self) -> &Action {
|
||||
&self.action
|
||||
}
|
||||
|
||||
fn private_port(&self) -> Option<u16> {
|
||||
Some(self.options.port)
|
||||
}
|
||||
|
||||
fn start_timeout(&self) -> Timeout<u32> {
|
||||
Timeout::Default
|
||||
}
|
||||
|
||||
fn total_cpus(&self) -> String {
|
||||
self.total_cpus.to_string()
|
||||
}
|
||||
|
||||
fn cpu_burst(&self) -> String {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn total_ram_in_mib(&self) -> u32 {
|
||||
self.total_ram_in_mib
|
||||
}
|
||||
|
||||
fn min_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn max_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn publicly_accessible(&self) -> bool {
|
||||
self.options.publicly_accessible
|
||||
}
|
||||
|
||||
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 = kubernetes.get_kubeconfig_file_path()?;
|
||||
|
||||
context.insert("kubeconfig_path", &kube_config_file_path);
|
||||
|
||||
kubectl::kubectl_exec_create_namespace_without_labels(
|
||||
environment.namespace(),
|
||||
kube_config_file_path.as_str(),
|
||||
kubernetes.cloud_provider().credentials_environment_variables(),
|
||||
);
|
||||
|
||||
let version = self
|
||||
.matching_correct_version(self.is_managed_service(), event_details.clone())?
|
||||
.matched_version()
|
||||
.to_string();
|
||||
|
||||
let parameter_group_name = if version.starts_with("5.") {
|
||||
"default.redis5.0"
|
||||
} else if version.starts_with("6.") {
|
||||
"default.redis6.x"
|
||||
} else {
|
||||
return Err(EngineError::new_terraform_unsupported_context_parameter_value(
|
||||
event_details,
|
||||
"Elasicache".to_string(),
|
||||
"database_elasticache_parameter_group_name".to_string(),
|
||||
format!("default.redis{}", version),
|
||||
None,
|
||||
));
|
||||
};
|
||||
|
||||
context.insert("database_elasticache_parameter_group_name", parameter_group_name);
|
||||
|
||||
context.insert("namespace", environment.namespace());
|
||||
context.insert("version", version.as_str());
|
||||
|
||||
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
|
||||
context.insert(k, v);
|
||||
}
|
||||
|
||||
context.insert("kubernetes_cluster_id", kubernetes.id());
|
||||
context.insert("kubernetes_cluster_name", kubernetes.name());
|
||||
|
||||
context.insert("fqdn_id", self.fqdn_id.as_str());
|
||||
context.insert("fqdn", self.fqdn(target, &self.fqdn, self.is_managed_service()).as_str());
|
||||
context.insert("service_name", self.fqdn_id.as_str());
|
||||
context.insert("database_login", self.options.login.as_str());
|
||||
context.insert("database_password", self.options.password.as_str());
|
||||
context.insert("database_port", &self.private_port());
|
||||
context.insert("database_disk_size_in_gib", &self.options.disk_size_in_gib);
|
||||
context.insert("database_instance_type", &self.database_instance_type);
|
||||
context.insert("database_disk_type", &self.options.database_disk_type);
|
||||
context.insert("database_ram_size_in_mib", &self.total_ram_in_mib);
|
||||
context.insert("database_total_cpus", &self.total_cpus);
|
||||
context.insert("database_fqdn", &self.options.host.as_str());
|
||||
context.insert("database_id", &self.id());
|
||||
context.insert("tfstate_suffix_name", &get_tfstate_suffix(self));
|
||||
context.insert("tfstate_name", &get_tfstate_name(self));
|
||||
context.insert("publicly_accessible", &self.options.publicly_accessible);
|
||||
|
||||
context.insert("skip_final_snapshot", &false);
|
||||
context.insert("final_snapshot_name", &aws_final_snapshot_name(self.id()));
|
||||
context.insert("delete_automated_backups", &self.context().is_test_cluster());
|
||||
if self.context.resource_expiration_in_seconds().is_some() {
|
||||
context.insert("resource_expiration_in_seconds", &self.context.resource_expiration_in_seconds())
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
fn logger(&self) -> &dyn Logger {
|
||||
&*self.logger
|
||||
}
|
||||
|
||||
fn selector(&self) -> Option<String> {
|
||||
Some(format!("app={}", self.sanitized_name()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Database for RedisAws {}
|
||||
|
||||
impl Helm for RedisAws {
|
||||
fn helm_selector(&self) -> Option<String> {
|
||||
self.selector()
|
||||
}
|
||||
|
||||
fn helm_release_name(&self) -> String {
|
||||
crate::string::cut(format!("redis-{}", self.id()), 50)
|
||||
}
|
||||
|
||||
fn helm_chart_dir(&self) -> String {
|
||||
format!("{}/common/services/redis", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_values_dir(&self) -> String {
|
||||
format!("{}/aws/chart_values/redis", self.context.lib_root_dir()) // FIXME replace `chart_values` by `charts_values`
|
||||
}
|
||||
|
||||
fn helm_chart_external_name_service_dir(&self) -> String {
|
||||
format!("{}/common/charts/external-name-svc", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Terraform for RedisAws {
|
||||
fn terraform_common_resource_dir_path(&self) -> String {
|
||||
format!("{}/aws/services/common", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn terraform_resource_dir_path(&self) -> String {
|
||||
format!("{}/aws/services/redis", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Create for RedisAws {
|
||||
#[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_stateful_service(target, self, event_details.clone(), self.logger())
|
||||
})
|
||||
}
|
||||
|
||||
fn on_create_check(&self) -> Result<(), EngineError> {
|
||||
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
|
||||
self.check_domains(self.listeners.clone(), vec![self.fqdn.as_str()], event_details, self.logger())
|
||||
}
|
||||
|
||||
#[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 RedisAws {
|
||||
#[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, || {
|
||||
scale_down_database(target, self, 0)
|
||||
})
|
||||
}
|
||||
|
||||
fn on_pause_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 RedisAws {
|
||||
#[named]
|
||||
fn on_delete(&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.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())
|
||||
})
|
||||
}
|
||||
|
||||
fn on_delete_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 RedisAws {
|
||||
fn listeners(&self) -> &Listeners {
|
||||
&self.listeners
|
||||
}
|
||||
|
||||
fn add_listener(&mut self, listener: Listener) {
|
||||
self.listeners.push(listener);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_redis_version(requested_version: String, is_managed_service: bool) -> Result<String, CommandError> {
|
||||
if is_managed_service {
|
||||
get_managed_redis_version(requested_version)
|
||||
} else {
|
||||
get_self_hosted_redis_version(requested_version)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_managed_redis_version(requested_version: String) -> Result<String, CommandError> {
|
||||
let mut supported_redis_versions = HashMap::with_capacity(2);
|
||||
// https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/supported-engine-versions.html
|
||||
|
||||
supported_redis_versions.insert("6".to_string(), "6.x".to_string());
|
||||
supported_redis_versions.insert("5".to_string(), "5.0.6".to_string());
|
||||
|
||||
get_supported_version_to_use("Elasticache", supported_redis_versions, requested_version)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::cloud_provider::aws::databases::redis::get_redis_version;
|
||||
use crate::errors::ErrorMessageVerbosity;
|
||||
|
||||
#[test]
|
||||
fn check_redis_version() {
|
||||
// managed version
|
||||
assert_eq!(get_redis_version("6".to_string(), true).unwrap(), "6.x");
|
||||
assert_eq!(get_redis_version("5".to_string(), true).unwrap(), "5.0.6");
|
||||
assert!(get_redis_version("1.0".to_string(), true)
|
||||
.unwrap_err()
|
||||
.message(ErrorMessageVerbosity::FullDetails)
|
||||
.contains("Elasticache 1.0 version is not supported"));
|
||||
|
||||
// self-hosted version
|
||||
assert_eq!(get_redis_version("6".to_string(), false).unwrap(), "6.0.9");
|
||||
assert_eq!(get_redis_version("6.0".to_string(), false).unwrap(), "6.0.9");
|
||||
assert!(get_redis_version("1.0".to_string(), false)
|
||||
.unwrap_err()
|
||||
.message(ErrorMessageVerbosity::FullDetails)
|
||||
.contains("Redis 1.0 version is not supported"));
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
use crate::cloud_provider::utilities::VersionsNumber;
|
||||
use crate::errors::CommandError;
|
||||
use crate::io_models::DatabaseKind;
|
||||
|
||||
pub fn get_parameter_group_from_version(
|
||||
version: VersionsNumber,
|
||||
database_kind: DatabaseKind,
|
||||
) -> Result<String, CommandError> {
|
||||
if version.minor.is_none() {
|
||||
return Err(CommandError::new_from_safe_message(format!(
|
||||
"Can't determine the minor version, to select parameter group for {:?} version {}",
|
||||
database_kind, version
|
||||
)));
|
||||
};
|
||||
|
||||
match database_kind {
|
||||
DatabaseKind::Mysql => Ok(format!("mysql{}.{}", version.major, version.minor.unwrap())),
|
||||
_ => Ok("".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
// name of the last snapshot before the database get deleted
|
||||
pub fn aws_final_snapshot_name(database_name: &str) -> String {
|
||||
format!("qovery-{}-final-snap", database_name)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_aws_databases_parameters {
|
||||
use crate::cloud_provider::aws::databases::utilities::get_parameter_group_from_version;
|
||||
use crate::cloud_provider::utilities::VersionsNumber;
|
||||
use crate::errors::ErrorMessageVerbosity;
|
||||
use crate::io_models::DatabaseKind;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn check_rds_mysql_parameter_groups() {
|
||||
let mysql_parameter_group = get_parameter_group_from_version(
|
||||
VersionsNumber::from_str("5.7.0").expect("error while trying to get version from str"),
|
||||
DatabaseKind::Mysql,
|
||||
);
|
||||
assert_eq!(mysql_parameter_group.unwrap(), "mysql5.7");
|
||||
|
||||
let mysql_parameter_group = get_parameter_group_from_version(
|
||||
VersionsNumber::from_str("8.0").expect("error while trying to get version from str"),
|
||||
DatabaseKind::Mysql,
|
||||
);
|
||||
assert_eq!(mysql_parameter_group.unwrap(), "mysql8.0");
|
||||
|
||||
let mysql_parameter_group = get_parameter_group_from_version(
|
||||
VersionsNumber::from_str("8").expect("error while trying to get version from str"),
|
||||
DatabaseKind::Mysql,
|
||||
);
|
||||
assert!(mysql_parameter_group
|
||||
.unwrap_err()
|
||||
.message(ErrorMessageVerbosity::FullDetails)
|
||||
.contains("Can't determine the minor version, to select parameter group for Mysql version 8"));
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ use crate::events::{EventDetails, GeneralStep, Stage, ToTransmitter, Transmitter
|
||||
use crate::io_models::{Context, Listen, Listener, Listeners, QoveryIdentifier};
|
||||
use crate::runtime::block_on;
|
||||
|
||||
pub mod databases;
|
||||
pub mod kubernetes;
|
||||
pub mod regions;
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
pub mod mongodb;
|
||||
pub mod mysql;
|
||||
pub mod postgresql;
|
||||
pub mod redis;
|
||||
@@ -1,384 +0,0 @@
|
||||
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, 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, EventDetails, Stage, ToTransmitter, Transmitter};
|
||||
use crate::io_models::DatabaseMode::MANAGED;
|
||||
use crate::io_models::{Context, Listen, Listener, Listeners};
|
||||
use crate::logger::Logger;
|
||||
use ::function_name::named;
|
||||
|
||||
pub struct MongoDo {
|
||||
context: Context,
|
||||
id: String,
|
||||
action: Action,
|
||||
name: String,
|
||||
version: String,
|
||||
fqdn: String,
|
||||
fqdn_id: String,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: String,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
}
|
||||
|
||||
impl MongoDo {
|
||||
pub fn new(
|
||||
context: Context,
|
||||
id: &str,
|
||||
action: Action,
|
||||
name: &str,
|
||||
version: &str,
|
||||
fqdn: &str,
|
||||
fqdn_id: &str,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: &str,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
) -> Self {
|
||||
MongoDo {
|
||||
context,
|
||||
action,
|
||||
id: id.to_string(),
|
||||
name: name.to_string(),
|
||||
version: version.to_string(),
|
||||
fqdn: fqdn.to_string(),
|
||||
fqdn_id: fqdn_id.to_string(),
|
||||
total_cpus,
|
||||
total_ram_in_mib,
|
||||
database_instance_type: database_instance_type.to_string(),
|
||||
options,
|
||||
listeners,
|
||||
logger,
|
||||
}
|
||||
}
|
||||
|
||||
fn matching_correct_version(&self, event_details: EventDetails) -> Result<ServiceVersionCheckResult, EngineError> {
|
||||
check_service_version(
|
||||
get_self_hosted_mongodb_version(self.version()),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
}
|
||||
|
||||
fn cloud_provider_name(&self) -> &str {
|
||||
"digitalocean"
|
||||
}
|
||||
|
||||
fn struct_name(&self) -> &str {
|
||||
"mongodb"
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulService for MongoDo {
|
||||
fn as_stateful_service(&self) -> &dyn StatefulService {
|
||||
self
|
||||
}
|
||||
|
||||
fn is_managed_service(&self) -> bool {
|
||||
self.options.mode == MANAGED
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTransmitter for MongoDo {
|
||||
fn to_transmitter(&self) -> Transmitter {
|
||||
Transmitter::Database(self.id().to_string(), self.service_type().to_string(), self.name().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Service for MongoDo {
|
||||
fn context(&self) -> &Context {
|
||||
&self.context
|
||||
}
|
||||
|
||||
fn service_type(&self) -> ServiceType {
|
||||
ServiceType::Database(DatabaseType::MongoDB(&self.options))
|
||||
}
|
||||
|
||||
fn id(&self) -> &str {
|
||||
self.id.as_str()
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
fn sanitized_name(&self) -> String {
|
||||
sanitize_name("mongodb", self.name())
|
||||
}
|
||||
|
||||
fn version(&self) -> String {
|
||||
self.version.clone()
|
||||
}
|
||||
|
||||
fn action(&self) -> &Action {
|
||||
&self.action
|
||||
}
|
||||
|
||||
fn private_port(&self) -> Option<u16> {
|
||||
Some(self.options.port)
|
||||
}
|
||||
|
||||
fn start_timeout(&self) -> Timeout<u32> {
|
||||
Timeout::Value(600)
|
||||
}
|
||||
|
||||
fn total_cpus(&self) -> String {
|
||||
self.total_cpus.to_string()
|
||||
}
|
||||
|
||||
fn cpu_burst(&self) -> String {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn total_ram_in_mib(&self) -> u32 {
|
||||
self.total_ram_in_mib
|
||||
}
|
||||
|
||||
fn min_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn max_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn publicly_accessible(&self) -> bool {
|
||||
self.options.publicly_accessible
|
||||
}
|
||||
|
||||
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 = kubernetes.get_kubeconfig_file_path()?;
|
||||
context.insert("kubeconfig_path", &kube_config_file_path);
|
||||
|
||||
kubectl::kubectl_exec_create_namespace_without_labels(
|
||||
environment.namespace(),
|
||||
kube_config_file_path.as_str(),
|
||||
kubernetes.cloud_provider().credentials_environment_variables(),
|
||||
);
|
||||
|
||||
context.insert("namespace", environment.namespace());
|
||||
|
||||
let version = self
|
||||
.matching_correct_version(event_details)?
|
||||
.matched_version()
|
||||
.to_string();
|
||||
context.insert("version", &version);
|
||||
|
||||
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
|
||||
context.insert(k, v);
|
||||
}
|
||||
|
||||
context.insert("kubernetes_cluster_id", kubernetes.id());
|
||||
context.insert("kubernetes_cluster_name", kubernetes.name());
|
||||
|
||||
context.insert("fqdn_id", self.fqdn_id.as_str());
|
||||
context.insert("fqdn", self.fqdn(target, &self.fqdn, self.is_managed_service()).as_str());
|
||||
context.insert("service_name", self.fqdn_id.as_str());
|
||||
context.insert("database_db_name", self.name.as_str());
|
||||
context.insert("database_login", self.options.login.as_str());
|
||||
context.insert("database_password", self.options.password.as_str());
|
||||
context.insert("database_port", &self.private_port());
|
||||
context.insert("database_disk_size_in_gib", &self.options.disk_size_in_gib);
|
||||
context.insert("database_instance_type", &self.database_instance_type);
|
||||
context.insert("database_disk_type", &self.options.database_disk_type);
|
||||
context.insert("database_ram_size_in_mib", &self.total_ram_in_mib);
|
||||
context.insert("database_total_cpus", &self.total_cpus);
|
||||
context.insert("database_fqdn", &self.options.host.as_str());
|
||||
context.insert("database_id", &self.id());
|
||||
context.insert("tfstate_suffix_name", &get_tfstate_suffix(self));
|
||||
context.insert("tfstate_name", &get_tfstate_name(self));
|
||||
context.insert("publicly_accessible", &self.options.publicly_accessible);
|
||||
|
||||
if self.context.resource_expiration_in_seconds().is_some() {
|
||||
context.insert("resource_expiration_in_seconds", &self.context.resource_expiration_in_seconds())
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
fn logger(&self) -> &dyn Logger {
|
||||
&*self.logger
|
||||
}
|
||||
|
||||
fn selector(&self) -> Option<String> {
|
||||
Some(format!("app={}", self.sanitized_name()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Database for MongoDo {}
|
||||
|
||||
impl Helm for MongoDo {
|
||||
fn helm_selector(&self) -> Option<String> {
|
||||
self.selector()
|
||||
}
|
||||
|
||||
fn helm_release_name(&self) -> String {
|
||||
crate::string::cut(format!("mongodb-{}", self.id()), 50)
|
||||
}
|
||||
|
||||
fn helm_chart_dir(&self) -> String {
|
||||
format!("{}/common/services/mongodb", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_values_dir(&self) -> String {
|
||||
format!("{}/digitalocean/chart_values/mongodb", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_external_name_service_dir(&self) -> String {
|
||||
format!("{}/common/charts/external-name-svc", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Terraform for MongoDo {
|
||||
fn terraform_common_resource_dir_path(&self) -> String {
|
||||
format!("{}/digitalocean/services/common", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn terraform_resource_dir_path(&self) -> String {
|
||||
format!("{}/digitalocean/services/mongodb", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Create for MongoDo {
|
||||
#[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_stateful_service(target, self, event_details.clone(), self.logger())
|
||||
})
|
||||
}
|
||||
|
||||
fn on_create_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 MongoDo {
|
||||
#[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, || {
|
||||
scale_down_database(target, self, 0)
|
||||
})
|
||||
}
|
||||
|
||||
fn on_pause_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 MongoDo {
|
||||
#[named]
|
||||
fn on_delete(&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.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())
|
||||
})
|
||||
}
|
||||
|
||||
fn on_delete_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 MongoDo {
|
||||
fn listeners(&self) -> &Listeners {
|
||||
&self.listeners
|
||||
}
|
||||
|
||||
fn add_listener(&mut self, listener: Listener) {
|
||||
self.listeners.push(listener);
|
||||
}
|
||||
}
|
||||
@@ -1,388 +0,0 @@
|
||||
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, 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::errors::EngineError;
|
||||
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter};
|
||||
use crate::io_models::DatabaseMode::MANAGED;
|
||||
use crate::io_models::{Context, Listen, Listener, Listeners};
|
||||
use crate::logger::Logger;
|
||||
use ::function_name::named;
|
||||
|
||||
pub struct MySQLDo {
|
||||
context: Context,
|
||||
id: String,
|
||||
action: Action,
|
||||
name: String,
|
||||
version: String,
|
||||
fqdn: String,
|
||||
fqdn_id: String,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: String,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
}
|
||||
|
||||
impl MySQLDo {
|
||||
pub fn new(
|
||||
context: Context,
|
||||
id: &str,
|
||||
action: Action,
|
||||
name: &str,
|
||||
version: &str,
|
||||
fqdn: &str,
|
||||
fqdn_id: &str,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: &str,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
) -> Self {
|
||||
Self {
|
||||
context,
|
||||
action,
|
||||
id: id.to_string(),
|
||||
name: name.to_string(),
|
||||
version: version.to_string(),
|
||||
fqdn: fqdn.to_string(),
|
||||
fqdn_id: fqdn_id.to_string(),
|
||||
total_cpus,
|
||||
total_ram_in_mib,
|
||||
database_instance_type: database_instance_type.to_string(),
|
||||
options,
|
||||
listeners,
|
||||
logger,
|
||||
}
|
||||
}
|
||||
|
||||
fn matching_correct_version(&self, event_details: EventDetails) -> Result<ServiceVersionCheckResult, EngineError> {
|
||||
check_service_version(
|
||||
get_self_hosted_mysql_version(self.version()),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
}
|
||||
|
||||
fn cloud_provider_name(&self) -> &str {
|
||||
"digitalocean"
|
||||
}
|
||||
|
||||
fn struct_name(&self) -> &str {
|
||||
"mysql"
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulService for MySQLDo {
|
||||
fn as_stateful_service(&self) -> &dyn StatefulService {
|
||||
self
|
||||
}
|
||||
|
||||
fn is_managed_service(&self) -> bool {
|
||||
self.options.mode == MANAGED
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTransmitter for MySQLDo {
|
||||
fn to_transmitter(&self) -> Transmitter {
|
||||
Transmitter::Database(self.id().to_string(), self.service_type().to_string(), self.name().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Service for MySQLDo {
|
||||
fn context(&self) -> &Context {
|
||||
&self.context
|
||||
}
|
||||
|
||||
fn service_type(&self) -> ServiceType {
|
||||
ServiceType::Database(DatabaseType::MySQL(&self.options))
|
||||
}
|
||||
|
||||
fn id(&self) -> &str {
|
||||
self.id.as_str()
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
fn sanitized_name(&self) -> String {
|
||||
sanitize_name("mysql", self.name())
|
||||
}
|
||||
|
||||
fn version(&self) -> String {
|
||||
self.version.clone()
|
||||
}
|
||||
|
||||
fn action(&self) -> &Action {
|
||||
&self.action
|
||||
}
|
||||
|
||||
fn private_port(&self) -> Option<u16> {
|
||||
Some(self.options.port)
|
||||
}
|
||||
|
||||
fn start_timeout(&self) -> Timeout<u32> {
|
||||
Timeout::Value(600)
|
||||
}
|
||||
|
||||
fn total_cpus(&self) -> String {
|
||||
self.total_cpus.to_string()
|
||||
}
|
||||
|
||||
fn cpu_burst(&self) -> String {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn total_ram_in_mib(&self) -> u32 {
|
||||
self.total_ram_in_mib
|
||||
}
|
||||
|
||||
fn min_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn max_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn publicly_accessible(&self) -> bool {
|
||||
self.options.publicly_accessible
|
||||
}
|
||||
|
||||
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 = kubernetes.get_kubeconfig_file_path()?;
|
||||
context.insert("kubeconfig_path", &kube_config_file_path);
|
||||
|
||||
kubectl::kubectl_exec_create_namespace_without_labels(
|
||||
environment.namespace(),
|
||||
kube_config_file_path.as_str(),
|
||||
kubernetes.cloud_provider().credentials_environment_variables(),
|
||||
);
|
||||
|
||||
context.insert("namespace", environment.namespace());
|
||||
|
||||
let version = &self
|
||||
.matching_correct_version(event_details)?
|
||||
.matched_version()
|
||||
.to_string();
|
||||
context.insert("version", &version);
|
||||
|
||||
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
|
||||
context.insert(k, v);
|
||||
}
|
||||
|
||||
context.insert("kubernetes_cluster_id", kubernetes.id());
|
||||
context.insert("kubernetes_cluster_name", kubernetes.name());
|
||||
|
||||
context.insert("fqdn_id", self.fqdn_id.as_str());
|
||||
context.insert("fqdn", self.fqdn(target, &self.fqdn, self.is_managed_service()).as_str());
|
||||
context.insert("service_name", self.fqdn_id.as_str());
|
||||
context.insert("database_login", self.options.login.as_str());
|
||||
context.insert("database_password", self.options.password.as_str());
|
||||
context.insert("database_port", &self.private_port());
|
||||
context.insert("database_disk_size_in_gib", &self.options.disk_size_in_gib);
|
||||
context.insert("database_instance_type", &self.database_instance_type);
|
||||
context.insert("database_disk_type", &self.options.database_disk_type);
|
||||
context.insert("database_ram_size_in_mib", &self.total_ram_in_mib);
|
||||
context.insert("database_total_cpus", &self.total_cpus);
|
||||
context.insert("database_fqdn", &self.options.host.as_str());
|
||||
context.insert("database_id", &self.id());
|
||||
context.insert("tfstate_suffix_name", &get_tfstate_suffix(self));
|
||||
context.insert("tfstate_name", &get_tfstate_name(self));
|
||||
context.insert("publicly_accessible", &self.options.publicly_accessible);
|
||||
|
||||
context.insert("delete_automated_backups", &self.context().is_test_cluster());
|
||||
if self.context.resource_expiration_in_seconds().is_some() {
|
||||
context.insert("resource_expiration_in_seconds", &self.context.resource_expiration_in_seconds())
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
fn logger(&self) -> &dyn Logger {
|
||||
&*self.logger
|
||||
}
|
||||
|
||||
fn selector(&self) -> Option<String> {
|
||||
Some(format!("app={}", self.sanitized_name()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Database for MySQLDo {}
|
||||
|
||||
impl Helm for MySQLDo {
|
||||
fn helm_selector(&self) -> Option<String> {
|
||||
self.selector()
|
||||
}
|
||||
|
||||
fn helm_release_name(&self) -> String {
|
||||
crate::string::cut(format!("mysql-{}", self.id()), 50)
|
||||
}
|
||||
|
||||
fn helm_chart_dir(&self) -> String {
|
||||
format!("{}/common/services/mysql", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_values_dir(&self) -> String {
|
||||
format!("{}/digitalocean/chart_values/mysql", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_external_name_service_dir(&self) -> String {
|
||||
format!("{}/common/charts/external-name-svc", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Terraform for MySQLDo {
|
||||
fn terraform_common_resource_dir_path(&self) -> String {
|
||||
format!("{}/digitalocean/services/common", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn terraform_resource_dir_path(&self) -> String {
|
||||
format!("{}/digitalocean/services/mysql", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Create for MySQLDo {
|
||||
#[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,
|
||||
Box::new(|| deploy_stateful_service(target, self, event_details.clone(), self.logger())),
|
||||
)
|
||||
}
|
||||
|
||||
fn on_create_check(&self) -> Result<(), EngineError> {
|
||||
//FIXME : perform an actual check
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 MySQLDo {
|
||||
#[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, || {
|
||||
scale_down_database(target, self, 0)
|
||||
})
|
||||
}
|
||||
|
||||
fn on_pause_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 MySQLDo {
|
||||
#[named]
|
||||
fn on_delete(&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.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(), self.logger())),
|
||||
)
|
||||
}
|
||||
|
||||
fn on_delete_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 MySQLDo {
|
||||
fn listeners(&self) -> &Listeners {
|
||||
&self.listeners
|
||||
}
|
||||
|
||||
fn add_listener(&mut self, listener: Listener) {
|
||||
self.listeners.push(listener);
|
||||
}
|
||||
}
|
||||
@@ -1,390 +0,0 @@
|
||||
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, 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::errors::EngineError;
|
||||
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter};
|
||||
use crate::io_models::DatabaseMode::MANAGED;
|
||||
use crate::io_models::{Context, Listen, Listener, Listeners};
|
||||
use crate::logger::Logger;
|
||||
use ::function_name::named;
|
||||
|
||||
pub struct PostgresDo {
|
||||
context: Context,
|
||||
id: String,
|
||||
action: Action,
|
||||
name: String,
|
||||
version: String,
|
||||
fqdn: String,
|
||||
fqdn_id: String,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: String,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
}
|
||||
|
||||
impl PostgresDo {
|
||||
pub fn new(
|
||||
context: Context,
|
||||
id: &str,
|
||||
action: Action,
|
||||
name: &str,
|
||||
version: &str,
|
||||
fqdn: &str,
|
||||
fqdn_id: &str,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: &str,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
) -> Self {
|
||||
PostgresDo {
|
||||
context,
|
||||
action,
|
||||
id: id.to_string(),
|
||||
name: name.to_string(),
|
||||
version: version.to_string(),
|
||||
fqdn: fqdn.to_string(),
|
||||
fqdn_id: fqdn_id.to_string(),
|
||||
total_cpus,
|
||||
total_ram_in_mib,
|
||||
database_instance_type: database_instance_type.to_string(),
|
||||
options,
|
||||
listeners,
|
||||
logger,
|
||||
}
|
||||
}
|
||||
|
||||
fn matching_correct_version(&self, event_details: EventDetails) -> Result<ServiceVersionCheckResult, EngineError> {
|
||||
check_service_version(
|
||||
get_self_hosted_postgres_version(self.version()),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
}
|
||||
|
||||
fn cloud_provider_name(&self) -> &str {
|
||||
"digitalocean"
|
||||
}
|
||||
|
||||
fn struct_name(&self) -> &str {
|
||||
"postgresql"
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulService for PostgresDo {
|
||||
fn as_stateful_service(&self) -> &dyn StatefulService {
|
||||
self
|
||||
}
|
||||
|
||||
fn is_managed_service(&self) -> bool {
|
||||
self.options.mode == MANAGED
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTransmitter for PostgresDo {
|
||||
fn to_transmitter(&self) -> Transmitter {
|
||||
Transmitter::Database(self.id().to_string(), self.service_type().to_string(), self.name().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Service for PostgresDo {
|
||||
fn context(&self) -> &Context {
|
||||
&self.context
|
||||
}
|
||||
|
||||
fn service_type(&self) -> ServiceType {
|
||||
ServiceType::Database(DatabaseType::PostgreSQL(&self.options))
|
||||
}
|
||||
|
||||
fn id(&self) -> &str {
|
||||
self.id.as_str()
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
fn sanitized_name(&self) -> String {
|
||||
sanitize_name("postgresql", self.name())
|
||||
}
|
||||
|
||||
fn version(&self) -> String {
|
||||
self.version.clone()
|
||||
}
|
||||
|
||||
fn action(&self) -> &Action {
|
||||
&self.action
|
||||
}
|
||||
|
||||
fn private_port(&self) -> Option<u16> {
|
||||
Some(self.options.port)
|
||||
}
|
||||
|
||||
fn start_timeout(&self) -> Timeout<u32> {
|
||||
Timeout::Value(600)
|
||||
}
|
||||
|
||||
fn total_cpus(&self) -> String {
|
||||
self.total_cpus.to_string()
|
||||
}
|
||||
|
||||
fn cpu_burst(&self) -> String {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn total_ram_in_mib(&self) -> u32 {
|
||||
self.total_ram_in_mib
|
||||
}
|
||||
|
||||
fn min_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn max_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn publicly_accessible(&self) -> bool {
|
||||
self.options.publicly_accessible
|
||||
}
|
||||
|
||||
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 = kubernetes.get_kubeconfig_file_path()?;
|
||||
context.insert("kubeconfig_path", &kube_config_file_path);
|
||||
|
||||
kubectl::kubectl_exec_create_namespace_without_labels(
|
||||
environment.namespace(),
|
||||
kube_config_file_path.as_str(),
|
||||
kubernetes.cloud_provider().credentials_environment_variables(),
|
||||
);
|
||||
|
||||
context.insert("namespace", environment.namespace());
|
||||
|
||||
let version = self
|
||||
.matching_correct_version(event_details)?
|
||||
.matched_version()
|
||||
.to_string();
|
||||
context.insert("version", &version);
|
||||
|
||||
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
|
||||
context.insert(k, v);
|
||||
}
|
||||
|
||||
context.insert("kubernetes_cluster_id", kubernetes.id());
|
||||
context.insert("kubernetes_cluster_name", kubernetes.name());
|
||||
|
||||
context.insert("fqdn_id", self.fqdn_id.as_str());
|
||||
context.insert("fqdn", self.fqdn(target, &self.fqdn, self.is_managed_service()).as_str());
|
||||
context.insert("service_name", self.fqdn_id.as_str());
|
||||
context.insert("database_db_name", self.name());
|
||||
context.insert("database_login", self.options.login.as_str());
|
||||
context.insert("database_password", self.options.password.as_str());
|
||||
context.insert("database_port", &self.private_port());
|
||||
context.insert("database_disk_size_in_gib", &self.options.disk_size_in_gib);
|
||||
context.insert("database_instance_type", &self.database_instance_type);
|
||||
context.insert("database_disk_type", &self.options.database_disk_type);
|
||||
context.insert("database_ram_size_in_mib", &self.total_ram_in_mib);
|
||||
context.insert("database_total_cpus", &self.total_cpus);
|
||||
context.insert("database_fqdn", &self.options.host.as_str());
|
||||
context.insert("database_id", &self.id());
|
||||
context.insert("tfstate_suffix_name", &get_tfstate_suffix(self));
|
||||
context.insert("tfstate_name", &get_tfstate_name(self));
|
||||
context.insert("publicly_accessible", &self.options.publicly_accessible);
|
||||
|
||||
context.insert("delete_automated_backups", &self.context().is_test_cluster());
|
||||
|
||||
if self.context.resource_expiration_in_seconds().is_some() {
|
||||
context.insert("resource_expiration_in_seconds", &self.context.resource_expiration_in_seconds())
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
fn selector(&self) -> Option<String> {
|
||||
Some(format!("app={}", self.sanitized_name()))
|
||||
}
|
||||
|
||||
fn logger(&self) -> &dyn Logger {
|
||||
&*self.logger
|
||||
}
|
||||
}
|
||||
|
||||
impl Database for PostgresDo {}
|
||||
|
||||
impl Helm for PostgresDo {
|
||||
fn helm_selector(&self) -> Option<String> {
|
||||
self.selector()
|
||||
}
|
||||
|
||||
fn helm_release_name(&self) -> String {
|
||||
crate::string::cut(format!("postgresql-{}", self.id()), 50)
|
||||
}
|
||||
|
||||
fn helm_chart_dir(&self) -> String {
|
||||
format!("{}/common/services/postgresql", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_values_dir(&self) -> String {
|
||||
format!("{}/digitalocean/chart_values/postgresql", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_external_name_service_dir(&self) -> String {
|
||||
format!("{}/common/charts/external-name-svc", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Terraform for PostgresDo {
|
||||
fn terraform_common_resource_dir_path(&self) -> String {
|
||||
format!("{}/digitalocean/services/common", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn terraform_resource_dir_path(&self) -> String {
|
||||
format!("{}/digitalocean/services/postgresql", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Create for PostgresDo {
|
||||
#[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,
|
||||
Box::new(|| deploy_stateful_service(target, self, event_details.clone(), self.logger())),
|
||||
)
|
||||
}
|
||||
|
||||
fn on_create_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 PostgresDo {
|
||||
#[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, || {
|
||||
scale_down_database(target, self, 0)
|
||||
})
|
||||
}
|
||||
|
||||
fn on_pause_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 PostgresDo {
|
||||
#[named]
|
||||
fn on_delete(&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.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(), self.logger())),
|
||||
)
|
||||
}
|
||||
|
||||
fn on_delete_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 PostgresDo {
|
||||
fn listeners(&self) -> &Listeners {
|
||||
&self.listeners
|
||||
}
|
||||
|
||||
fn add_listener(&mut self, listener: Listener) {
|
||||
self.listeners.push(listener);
|
||||
}
|
||||
}
|
||||
@@ -1,385 +0,0 @@
|
||||
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, 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::errors::EngineError;
|
||||
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter};
|
||||
use crate::io_models::DatabaseMode::MANAGED;
|
||||
use crate::io_models::{Context, Listen, Listener, Listeners};
|
||||
use crate::logger::Logger;
|
||||
use ::function_name::named;
|
||||
|
||||
pub struct RedisDo {
|
||||
context: Context,
|
||||
id: String,
|
||||
action: Action,
|
||||
name: String,
|
||||
version: String,
|
||||
fqdn: String,
|
||||
fqdn_id: String,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: String,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
}
|
||||
|
||||
impl RedisDo {
|
||||
pub fn new(
|
||||
context: Context,
|
||||
id: &str,
|
||||
action: Action,
|
||||
name: &str,
|
||||
version: &str,
|
||||
fqdn: &str,
|
||||
fqdn_id: &str,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: &str,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
) -> Self {
|
||||
Self {
|
||||
context,
|
||||
action,
|
||||
id: id.to_string(),
|
||||
name: name.to_string(),
|
||||
version: version.to_string(),
|
||||
fqdn: fqdn.to_string(),
|
||||
fqdn_id: fqdn_id.to_string(),
|
||||
total_cpus,
|
||||
total_ram_in_mib,
|
||||
database_instance_type: database_instance_type.to_string(),
|
||||
options,
|
||||
listeners,
|
||||
logger,
|
||||
}
|
||||
}
|
||||
|
||||
fn matching_correct_version(&self, event_details: EventDetails) -> Result<ServiceVersionCheckResult, EngineError> {
|
||||
check_service_version(
|
||||
get_self_hosted_redis_version(self.version()),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
}
|
||||
|
||||
fn cloud_provider_name(&self) -> &str {
|
||||
"digitalocean"
|
||||
}
|
||||
|
||||
fn struct_name(&self) -> &str {
|
||||
"redis"
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulService for RedisDo {
|
||||
fn as_stateful_service(&self) -> &dyn StatefulService {
|
||||
self
|
||||
}
|
||||
|
||||
fn is_managed_service(&self) -> bool {
|
||||
self.options.mode == MANAGED
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTransmitter for RedisDo {
|
||||
fn to_transmitter(&self) -> Transmitter {
|
||||
Transmitter::Database(self.id().to_string(), self.service_type().to_string(), self.name().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Service for RedisDo {
|
||||
fn context(&self) -> &Context {
|
||||
&self.context
|
||||
}
|
||||
|
||||
fn service_type(&self) -> ServiceType {
|
||||
ServiceType::Database(DatabaseType::Redis(&self.options))
|
||||
}
|
||||
|
||||
fn id(&self) -> &str {
|
||||
self.id.as_str()
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
fn sanitized_name(&self) -> String {
|
||||
sanitize_name("redis", self.name())
|
||||
}
|
||||
|
||||
fn version(&self) -> String {
|
||||
self.version.clone()
|
||||
}
|
||||
|
||||
fn action(&self) -> &Action {
|
||||
&self.action
|
||||
}
|
||||
|
||||
fn private_port(&self) -> Option<u16> {
|
||||
Some(self.options.port)
|
||||
}
|
||||
|
||||
fn start_timeout(&self) -> Timeout<u32> {
|
||||
Timeout::Value(600)
|
||||
}
|
||||
|
||||
fn total_cpus(&self) -> String {
|
||||
self.total_cpus.to_string()
|
||||
}
|
||||
|
||||
fn cpu_burst(&self) -> String {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn total_ram_in_mib(&self) -> u32 {
|
||||
self.total_ram_in_mib
|
||||
}
|
||||
|
||||
fn min_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn max_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn publicly_accessible(&self) -> bool {
|
||||
self.options.publicly_accessible
|
||||
}
|
||||
|
||||
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 = kubernetes.get_kubeconfig_file_path()?;
|
||||
context.insert("kubeconfig_path", &kube_config_file_path);
|
||||
|
||||
kubectl::kubectl_exec_create_namespace_without_labels(
|
||||
environment.namespace(),
|
||||
kube_config_file_path.as_str(),
|
||||
kubernetes.cloud_provider().credentials_environment_variables(),
|
||||
);
|
||||
|
||||
let version = self
|
||||
.matching_correct_version(event_details)?
|
||||
.matched_version()
|
||||
.to_string();
|
||||
|
||||
context.insert("namespace", environment.namespace());
|
||||
context.insert("version", &version);
|
||||
|
||||
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
|
||||
context.insert(k, v);
|
||||
}
|
||||
|
||||
context.insert("kubernetes_cluster_id", kubernetes.id());
|
||||
context.insert("kubernetes_cluster_name", kubernetes.name());
|
||||
|
||||
context.insert("fqdn_id", self.fqdn_id.as_str());
|
||||
context.insert("fqdn", self.fqdn(target, &self.fqdn, self.is_managed_service()).as_str());
|
||||
context.insert("service_name", self.fqdn_id.as_str());
|
||||
context.insert("database_login", self.options.login.as_str());
|
||||
context.insert("database_password", self.options.password.as_str());
|
||||
context.insert("database_port", &self.private_port());
|
||||
context.insert("database_disk_size_in_gib", &self.options.disk_size_in_gib);
|
||||
context.insert("database_instance_type", &self.database_instance_type);
|
||||
context.insert("database_disk_type", &self.options.database_disk_type);
|
||||
context.insert("database_ram_size_in_mib", &self.total_ram_in_mib);
|
||||
context.insert("database_total_cpus", &self.total_cpus);
|
||||
context.insert("database_fqdn", &self.options.host.as_str());
|
||||
context.insert("database_id", &self.id());
|
||||
context.insert("tfstate_suffix_name", &get_tfstate_suffix(self));
|
||||
context.insert("tfstate_name", &get_tfstate_name(self));
|
||||
context.insert("publicly_accessible", &self.options.publicly_accessible);
|
||||
|
||||
if self.context.resource_expiration_in_seconds().is_some() {
|
||||
context.insert("resource_expiration_in_seconds", &self.context.resource_expiration_in_seconds())
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
fn logger(&self) -> &dyn Logger {
|
||||
&*self.logger
|
||||
}
|
||||
|
||||
fn selector(&self) -> Option<String> {
|
||||
Some(format!("app={}", self.sanitized_name()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Database for RedisDo {}
|
||||
|
||||
impl Helm for RedisDo {
|
||||
fn helm_selector(&self) -> Option<String> {
|
||||
self.selector()
|
||||
}
|
||||
|
||||
fn helm_release_name(&self) -> String {
|
||||
crate::string::cut(format!("redis-{}", self.id()), 50)
|
||||
}
|
||||
|
||||
fn helm_chart_dir(&self) -> String {
|
||||
format!("{}/common/services/redis", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_values_dir(&self) -> String {
|
||||
format!("{}/digitalocean/chart_values/redis", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_external_name_service_dir(&self) -> String {
|
||||
format!("{}/common/charts/external-name-svc", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Terraform for RedisDo {
|
||||
fn terraform_common_resource_dir_path(&self) -> String {
|
||||
format!("{}/digitalocean/services/common", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn terraform_resource_dir_path(&self) -> String {
|
||||
format!("{}/digitalocean/services/redis", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Create for RedisDo {
|
||||
#[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,
|
||||
Box::new(|| deploy_stateful_service(target, self, event_details.clone(), self.logger())),
|
||||
)
|
||||
}
|
||||
|
||||
fn on_create_check(&self) -> Result<(), EngineError> {
|
||||
//FIXME : perform an actual check
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 RedisDo {
|
||||
#[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, || {
|
||||
scale_down_database(target, self, 0)
|
||||
})
|
||||
}
|
||||
|
||||
fn on_pause_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 RedisDo {
|
||||
#[named]
|
||||
fn on_delete(&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.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(), self.logger())),
|
||||
)
|
||||
}
|
||||
|
||||
fn on_delete_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 RedisDo {
|
||||
fn listeners(&self) -> &Listeners {
|
||||
&self.listeners
|
||||
}
|
||||
|
||||
fn add_listener(&mut self, listener: Listener) {
|
||||
self.listeners.push(listener);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::cloud_provider::digitalocean::do_api_common::{do_get_from_api, DoApiType};
|
||||
use crate::cloud_provider::digitalocean::models::doks::KubernetesCluster;
|
||||
use crate::cloud_provider::digitalocean::models::doks::{DoksList, DoksOptions, KubernetesVersion};
|
||||
use crate::cloud_provider::utilities::VersionsNumber;
|
||||
use crate::errors::CommandError;
|
||||
use crate::models::types::VersionsNumber;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub fn get_doks_info_from_name(
|
||||
|
||||
@@ -25,7 +25,7 @@ use crate::cloud_provider::kubernetes::{
|
||||
};
|
||||
use crate::cloud_provider::models::NodeGroups;
|
||||
use crate::cloud_provider::qovery::EngineLocation;
|
||||
use crate::cloud_provider::utilities::{print_action, VersionsNumber};
|
||||
use crate::cloud_provider::utilities::print_action;
|
||||
use crate::cloud_provider::{kubernetes, CloudProvider};
|
||||
use crate::cmd::helm::{to_engine_error, Helm};
|
||||
use crate::cmd::kubectl::{
|
||||
@@ -45,6 +45,7 @@ use crate::io_models::{
|
||||
};
|
||||
use crate::logger::Logger;
|
||||
use crate::models::digital_ocean::DoRegion;
|
||||
use crate::models::types::VersionsNumber;
|
||||
use crate::object_storage::spaces::{BucketDeleteStrategy, Spaces};
|
||||
use crate::object_storage::ObjectStorage;
|
||||
use crate::runtime::block_on;
|
||||
|
||||
@@ -11,7 +11,6 @@ use crate::errors::EngineError;
|
||||
use crate::events::{EventDetails, GeneralStep, Stage, ToTransmitter, Transmitter};
|
||||
use crate::io_models::{Context, Listen, Listener, Listeners, QoveryIdentifier};
|
||||
|
||||
pub mod databases;
|
||||
pub mod do_api_common;
|
||||
pub mod kubernetes;
|
||||
pub mod models;
|
||||
|
||||
@@ -18,7 +18,6 @@ use crate::cloud_provider::aws::regions::AwsZones;
|
||||
use crate::cloud_provider::environment::Environment;
|
||||
use crate::cloud_provider::models::{CpuLimits, NodeGroups};
|
||||
use crate::cloud_provider::service::CheckAction;
|
||||
use crate::cloud_provider::utilities::VersionsNumber;
|
||||
use crate::cloud_provider::{service, CloudProvider, DeploymentTarget};
|
||||
use crate::cmd::kubectl;
|
||||
use crate::cmd::kubectl::{
|
||||
@@ -36,6 +35,7 @@ use crate::io_models::{
|
||||
Action, Context, Listen, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope, QoveryIdentifier, StringPath,
|
||||
};
|
||||
use crate::logger::Logger;
|
||||
use crate::models::types::VersionsNumber;
|
||||
use crate::object_storage::ObjectStorage;
|
||||
use crate::unit_conversion::{any_to_mi, cpu_string_to_float};
|
||||
|
||||
@@ -1425,18 +1425,18 @@ pub fn convert_k8s_cpu_value_to_f32(value: String) -> Result<f32, CommandError>
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::cloud_provider::Kind::Aws;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::cloud_provider::kubernetes::{
|
||||
check_kubernetes_upgrade_status, compare_kubernetes_cluster_versions_for_upgrade, convert_k8s_cpu_value_to_f32,
|
||||
validate_k8s_required_cpu_and_burstable, KubernetesNodesType,
|
||||
};
|
||||
use crate::cloud_provider::models::CpuLimits;
|
||||
use crate::cloud_provider::utilities::VersionsNumber;
|
||||
use crate::cmd::structs::{KubernetesList, KubernetesNode, KubernetesVersion};
|
||||
use crate::events::{EventDetails, InfrastructureStep, Stage, Transmitter};
|
||||
use crate::io_models::{ListenersHelper, QoveryIdentifier};
|
||||
use crate::logger::StdIoLogger;
|
||||
use crate::models::types::VersionsNumber;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
pub fn check_kubernetes_upgrade_method() {
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
pub mod mongodb;
|
||||
pub mod mysql;
|
||||
pub mod postgresql;
|
||||
pub mod redis;
|
||||
@@ -1,385 +0,0 @@
|
||||
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, 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, EventDetails, Stage, ToTransmitter, Transmitter};
|
||||
use crate::io_models::DatabaseMode::MANAGED;
|
||||
use crate::io_models::{Context, Listen, Listener, Listeners};
|
||||
use crate::logger::Logger;
|
||||
use ::function_name::named;
|
||||
|
||||
pub struct MongoDbScw {
|
||||
context: Context,
|
||||
id: String,
|
||||
action: Action,
|
||||
name: String,
|
||||
version: String,
|
||||
fqdn: String,
|
||||
fqdn_id: String,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: String,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
}
|
||||
|
||||
impl MongoDbScw {
|
||||
pub fn new(
|
||||
context: Context,
|
||||
id: &str,
|
||||
action: Action,
|
||||
name: &str,
|
||||
version: &str,
|
||||
fqdn: &str,
|
||||
fqdn_id: &str,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: &str,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
) -> Self {
|
||||
MongoDbScw {
|
||||
context,
|
||||
action,
|
||||
id: id.to_string(),
|
||||
name: name.to_string(),
|
||||
version: version.to_string(),
|
||||
fqdn: fqdn.to_string(),
|
||||
fqdn_id: fqdn_id.to_string(),
|
||||
total_cpus,
|
||||
total_ram_in_mib,
|
||||
database_instance_type: database_instance_type.to_string(),
|
||||
options,
|
||||
listeners,
|
||||
logger,
|
||||
}
|
||||
}
|
||||
|
||||
fn matching_correct_version(&self, event_details: EventDetails) -> Result<ServiceVersionCheckResult, EngineError> {
|
||||
check_service_version(
|
||||
get_self_hosted_mongodb_version(self.version()),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
}
|
||||
|
||||
fn cloud_provider_name(&self) -> &str {
|
||||
"scaleway"
|
||||
}
|
||||
|
||||
fn struct_name(&self) -> &str {
|
||||
"mongodb"
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulService for MongoDbScw {
|
||||
fn as_stateful_service(&self) -> &dyn StatefulService {
|
||||
self
|
||||
}
|
||||
|
||||
fn is_managed_service(&self) -> bool {
|
||||
self.options.mode == MANAGED
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTransmitter for MongoDbScw {
|
||||
fn to_transmitter(&self) -> Transmitter {
|
||||
Transmitter::Database(self.id().to_string(), self.service_type().to_string(), self.name().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Service for MongoDbScw {
|
||||
fn context(&self) -> &Context {
|
||||
&self.context
|
||||
}
|
||||
|
||||
fn service_type(&self) -> ServiceType {
|
||||
ServiceType::Database(DatabaseType::MongoDB(&self.options))
|
||||
}
|
||||
|
||||
fn id(&self) -> &str {
|
||||
self.id.as_str()
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
fn sanitized_name(&self) -> String {
|
||||
sanitize_name("mongodb", self.name())
|
||||
}
|
||||
|
||||
fn version(&self) -> String {
|
||||
self.version.clone()
|
||||
}
|
||||
|
||||
fn action(&self) -> &Action {
|
||||
&self.action
|
||||
}
|
||||
|
||||
fn private_port(&self) -> Option<u16> {
|
||||
Some(self.options.port)
|
||||
}
|
||||
|
||||
fn start_timeout(&self) -> Timeout<u32> {
|
||||
Timeout::Value(600)
|
||||
}
|
||||
|
||||
fn total_cpus(&self) -> String {
|
||||
self.total_cpus.to_string()
|
||||
}
|
||||
|
||||
fn cpu_burst(&self) -> String {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn total_ram_in_mib(&self) -> u32 {
|
||||
self.total_ram_in_mib
|
||||
}
|
||||
|
||||
fn min_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn max_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn publicly_accessible(&self) -> bool {
|
||||
self.options.publicly_accessible
|
||||
}
|
||||
|
||||
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 = kubernetes.get_kubeconfig_file_path()?;
|
||||
|
||||
context.insert("kubeconfig_path", &kube_config_file_path);
|
||||
|
||||
kubectl::kubectl_exec_create_namespace_without_labels(
|
||||
environment.namespace(),
|
||||
kube_config_file_path.as_str(),
|
||||
kubernetes.cloud_provider().credentials_environment_variables(),
|
||||
);
|
||||
|
||||
context.insert("namespace", environment.namespace());
|
||||
|
||||
let version = self
|
||||
.matching_correct_version(event_details)?
|
||||
.matched_version()
|
||||
.to_string();
|
||||
context.insert("version", &version);
|
||||
|
||||
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
|
||||
context.insert(k, v);
|
||||
}
|
||||
|
||||
context.insert("kubernetes_cluster_id", kubernetes.id());
|
||||
context.insert("kubernetes_cluster_name", kubernetes.name());
|
||||
|
||||
context.insert("fqdn_id", self.fqdn_id.as_str());
|
||||
context.insert("fqdn", self.fqdn(target, &self.fqdn, self.is_managed_service()).as_str());
|
||||
context.insert("service_name", self.fqdn_id.as_str());
|
||||
context.insert("database_db_name", self.name.as_str());
|
||||
context.insert("database_login", self.options.login.as_str());
|
||||
context.insert("database_password", self.options.password.as_str());
|
||||
context.insert("database_port", &self.private_port());
|
||||
context.insert("database_disk_size_in_gib", &self.options.disk_size_in_gib);
|
||||
context.insert("database_instance_type", &self.database_instance_type);
|
||||
context.insert("database_disk_type", &self.options.database_disk_type);
|
||||
context.insert("database_ram_size_in_mib", &self.total_ram_in_mib);
|
||||
context.insert("database_total_cpus", &self.total_cpus);
|
||||
context.insert("database_fqdn", &self.options.host.as_str());
|
||||
context.insert("database_id", &self.id());
|
||||
context.insert("tfstate_suffix_name", &get_tfstate_suffix(self));
|
||||
context.insert("tfstate_name", &get_tfstate_name(self));
|
||||
context.insert("publicly_accessible", &self.options.publicly_accessible);
|
||||
|
||||
if self.context.resource_expiration_in_seconds().is_some() {
|
||||
context.insert("resource_expiration_in_seconds", &self.context.resource_expiration_in_seconds())
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
fn logger(&self) -> &dyn Logger {
|
||||
&*self.logger
|
||||
}
|
||||
|
||||
fn selector(&self) -> Option<String> {
|
||||
Some(format!("app={}", self.sanitized_name()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Database for MongoDbScw {}
|
||||
|
||||
impl Helm for MongoDbScw {
|
||||
fn helm_selector(&self) -> Option<String> {
|
||||
self.selector()
|
||||
}
|
||||
|
||||
fn helm_release_name(&self) -> String {
|
||||
crate::string::cut(format!("mongodb-{}", self.id()), 50)
|
||||
}
|
||||
|
||||
fn helm_chart_dir(&self) -> String {
|
||||
format!("{}/common/services/mongodb", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_values_dir(&self) -> String {
|
||||
format!("{}/scaleway/chart_values/mongodb", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_external_name_service_dir(&self) -> String {
|
||||
format!("{}/common/charts/external-name-svc", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Terraform for MongoDbScw {
|
||||
fn terraform_common_resource_dir_path(&self) -> String {
|
||||
format!("{}/scaleway/services/common", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn terraform_resource_dir_path(&self) -> String {
|
||||
format!("{}/scaleway/services/mongodb", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Create for MongoDbScw {
|
||||
#[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_stateful_service(target, self, event_details.clone(), self.logger())
|
||||
})
|
||||
}
|
||||
|
||||
fn on_create_check(&self) -> Result<(), EngineError> {
|
||||
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
|
||||
self.check_domains(self.listeners.clone(), vec![self.fqdn.as_str()], event_details, self.logger())
|
||||
}
|
||||
|
||||
#[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 MongoDbScw {
|
||||
#[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, || {
|
||||
scale_down_database(target, self, 0)
|
||||
})
|
||||
}
|
||||
|
||||
fn on_pause_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 MongoDbScw {
|
||||
#[named]
|
||||
fn on_delete(&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.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())
|
||||
})
|
||||
}
|
||||
|
||||
fn on_delete_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 MongoDbScw {
|
||||
fn listeners(&self) -> &Listeners {
|
||||
&self.listeners
|
||||
}
|
||||
|
||||
fn add_listener(&mut self, listener: Listener) {
|
||||
self.listeners.push(listener);
|
||||
}
|
||||
}
|
||||
@@ -1,417 +0,0 @@
|
||||
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, ServiceVersionCheckResult, StatefulService, Terraform,
|
||||
};
|
||||
use crate::cloud_provider::utilities::{
|
||||
get_self_hosted_mysql_version, get_supported_version_to_use, print_action, sanitize_name, VersionsNumber,
|
||||
};
|
||||
use crate::cloud_provider::DeploymentTarget;
|
||||
use crate::cmd::helm::Timeout;
|
||||
use crate::cmd::kubectl;
|
||||
use crate::errors::{CommandError, EngineError};
|
||||
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter};
|
||||
use crate::io_models::DatabaseMode::MANAGED;
|
||||
use crate::io_models::{Context, Listen, Listener, Listeners};
|
||||
use crate::logger::Logger;
|
||||
use ::function_name::named;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct MySQLScw {
|
||||
context: Context,
|
||||
id: String,
|
||||
action: Action,
|
||||
name: String,
|
||||
version: VersionsNumber,
|
||||
fqdn: String,
|
||||
fqdn_id: String,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: String,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
}
|
||||
|
||||
impl MySQLScw {
|
||||
pub fn new(
|
||||
context: Context,
|
||||
id: &str,
|
||||
action: Action,
|
||||
name: &str,
|
||||
version: VersionsNumber,
|
||||
fqdn: &str,
|
||||
fqdn_id: &str,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: &str,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
) -> Self {
|
||||
Self {
|
||||
context,
|
||||
action,
|
||||
id: id.to_string(),
|
||||
name: name.to_string(),
|
||||
version,
|
||||
fqdn: fqdn.to_string(),
|
||||
fqdn_id: fqdn_id.to_string(),
|
||||
total_cpus,
|
||||
total_ram_in_mib,
|
||||
database_instance_type: database_instance_type.to_string(),
|
||||
options,
|
||||
listeners,
|
||||
logger,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
self.logger(),
|
||||
)
|
||||
}
|
||||
|
||||
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 {
|
||||
get_self_hosted_mysql_version(requested_version)
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// {"name": "MySQL", "version":"8","end_of_life":"2026-04-01T00:00:00Z"}
|
||||
supported_mysql_versions.insert("8".to_string(), "8".to_string());
|
||||
supported_mysql_versions.insert("8.0".to_string(), "8.0".to_string());
|
||||
|
||||
get_supported_version_to_use("RDB MySQL", supported_mysql_versions, requested_version)
|
||||
}
|
||||
|
||||
fn cloud_provider_name(&self) -> &str {
|
||||
"scaleway"
|
||||
}
|
||||
|
||||
fn struct_name(&self) -> &str {
|
||||
"mysql"
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulService for MySQLScw {
|
||||
fn as_stateful_service(&self) -> &dyn StatefulService {
|
||||
self
|
||||
}
|
||||
|
||||
fn is_managed_service(&self) -> bool {
|
||||
self.options.mode == MANAGED
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTransmitter for MySQLScw {
|
||||
fn to_transmitter(&self) -> Transmitter {
|
||||
Transmitter::Database(self.id().to_string(), self.service_type().to_string(), self.name().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Service for MySQLScw {
|
||||
fn context(&self) -> &Context {
|
||||
&self.context
|
||||
}
|
||||
|
||||
fn service_type(&self) -> ServiceType {
|
||||
ServiceType::Database(DatabaseType::MySQL(&self.options))
|
||||
}
|
||||
|
||||
fn id(&self) -> &str {
|
||||
self.id.as_str()
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
fn sanitized_name(&self) -> String {
|
||||
sanitize_name("mysql", self.name())
|
||||
}
|
||||
|
||||
fn version(&self) -> String {
|
||||
self.version.to_string()
|
||||
}
|
||||
|
||||
fn action(&self) -> &Action {
|
||||
&self.action
|
||||
}
|
||||
|
||||
fn private_port(&self) -> Option<u16> {
|
||||
Some(self.options.port)
|
||||
}
|
||||
|
||||
fn start_timeout(&self) -> Timeout<u32> {
|
||||
Timeout::Value(600)
|
||||
}
|
||||
|
||||
fn total_cpus(&self) -> String {
|
||||
self.total_cpus.to_string()
|
||||
}
|
||||
|
||||
fn cpu_burst(&self) -> String {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn total_ram_in_mib(&self) -> u32 {
|
||||
self.total_ram_in_mib
|
||||
}
|
||||
|
||||
fn min_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn max_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn publicly_accessible(&self) -> bool {
|
||||
self.options.publicly_accessible
|
||||
}
|
||||
|
||||
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 = kubernetes.get_kubeconfig_file_path()?;
|
||||
context.insert("kubeconfig_path", &kube_config_file_path);
|
||||
|
||||
kubectl::kubectl_exec_create_namespace_without_labels(
|
||||
environment.namespace(),
|
||||
kube_config_file_path.as_str(),
|
||||
kubernetes.cloud_provider().credentials_environment_variables(),
|
||||
);
|
||||
|
||||
context.insert("namespace", environment.namespace());
|
||||
|
||||
let version = &self
|
||||
.matching_correct_version(self.is_managed_service(), event_details)?
|
||||
.matched_version();
|
||||
context.insert("version_major", &version.to_major_version_string());
|
||||
context.insert("version", &version.to_string()); // Scaleway needs to have major version only
|
||||
|
||||
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
|
||||
context.insert(k, v);
|
||||
}
|
||||
|
||||
context.insert("kubernetes_cluster_id", kubernetes.id());
|
||||
context.insert("kubernetes_cluster_name", kubernetes.name());
|
||||
|
||||
context.insert("fqdn_id", self.fqdn_id.as_str());
|
||||
context.insert("fqdn", self.fqdn(target, &self.fqdn, self.is_managed_service()).as_str());
|
||||
context.insert("service_name", self.fqdn_id.as_str());
|
||||
context.insert("database_login", self.options.login.as_str());
|
||||
context.insert("database_password", self.options.password.as_str());
|
||||
context.insert("database_port", &self.private_port());
|
||||
context.insert("database_disk_size_in_gib", &self.options.disk_size_in_gib);
|
||||
context.insert("database_instance_type", &self.database_instance_type);
|
||||
context.insert("database_disk_type", &self.options.database_disk_type);
|
||||
context.insert("database_name", &self.sanitized_name());
|
||||
context.insert("database_ram_size_in_mib", &self.total_ram_in_mib);
|
||||
context.insert("database_total_cpus", &self.total_cpus);
|
||||
context.insert("database_fqdn", &self.options.host.as_str());
|
||||
context.insert("database_id", &self.id());
|
||||
context.insert("tfstate_suffix_name", &get_tfstate_suffix(self));
|
||||
context.insert("tfstate_name", &get_tfstate_name(self));
|
||||
|
||||
context.insert("publicly_accessible", &self.options.publicly_accessible);
|
||||
context.insert("activate_high_availability", &self.options.activate_high_availability);
|
||||
context.insert("activate_backups", &self.options.activate_backups);
|
||||
|
||||
context.insert("delete_automated_backups", &self.context().is_test_cluster());
|
||||
context.insert("skip_final_snapshot", &self.context().is_test_cluster());
|
||||
if self.context.resource_expiration_in_seconds().is_some() {
|
||||
context.insert("resource_expiration_in_seconds", &self.context.resource_expiration_in_seconds())
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
fn logger(&self) -> &dyn Logger {
|
||||
&*self.logger
|
||||
}
|
||||
|
||||
fn selector(&self) -> Option<String> {
|
||||
Some(format!("app={}", self.sanitized_name()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Database for MySQLScw {}
|
||||
|
||||
impl Helm for MySQLScw {
|
||||
fn helm_selector(&self) -> Option<String> {
|
||||
self.selector()
|
||||
}
|
||||
|
||||
fn helm_release_name(&self) -> String {
|
||||
crate::string::cut(format!("mysql-{}", self.id()), 50)
|
||||
}
|
||||
|
||||
fn helm_chart_dir(&self) -> String {
|
||||
format!("{}/common/services/mysql", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_values_dir(&self) -> String {
|
||||
format!("{}/scaleway/chart_values/mysql", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_external_name_service_dir(&self) -> String {
|
||||
format!("{}/common/charts/external-name-svc", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Terraform for MySQLScw {
|
||||
fn terraform_common_resource_dir_path(&self) -> String {
|
||||
format!("{}/scaleway/services/common", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn terraform_resource_dir_path(&self) -> String {
|
||||
format!("{}/scaleway/services/mysql", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Create for MySQLScw {
|
||||
#[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_stateful_service(target, self, event_details.clone(), self.logger())
|
||||
})
|
||||
}
|
||||
|
||||
fn on_create_check(&self) -> Result<(), EngineError> {
|
||||
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
|
||||
self.check_domains(self.listeners.clone(), vec![self.fqdn.as_str()], event_details, self.logger())
|
||||
}
|
||||
|
||||
#[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 MySQLScw {
|
||||
#[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, || {
|
||||
scale_down_database(target, self, 0)
|
||||
})
|
||||
}
|
||||
|
||||
fn on_pause_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 MySQLScw {
|
||||
#[named]
|
||||
fn on_delete(&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.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())
|
||||
})
|
||||
}
|
||||
|
||||
fn on_delete_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 MySQLScw {
|
||||
fn listeners(&self) -> &Listeners {
|
||||
&self.listeners
|
||||
}
|
||||
|
||||
fn add_listener(&mut self, listener: Listener) {
|
||||
self.listeners.push(listener);
|
||||
}
|
||||
}
|
||||
@@ -1,426 +0,0 @@
|
||||
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, ServiceVersionCheckResult, StatefulService, Terraform,
|
||||
};
|
||||
use crate::cloud_provider::utilities::{
|
||||
get_self_hosted_postgres_version, get_supported_version_to_use, print_action, sanitize_name, VersionsNumber,
|
||||
};
|
||||
use crate::cloud_provider::DeploymentTarget;
|
||||
use crate::cmd::helm::Timeout;
|
||||
use crate::cmd::kubectl;
|
||||
use crate::errors::{CommandError, EngineError};
|
||||
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter};
|
||||
use crate::io_models::DatabaseMode::MANAGED;
|
||||
use crate::io_models::{Context, Listen, Listener, Listeners};
|
||||
use crate::logger::Logger;
|
||||
use ::function_name::named;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct PostgresScw {
|
||||
context: Context,
|
||||
id: String,
|
||||
action: Action,
|
||||
name: String,
|
||||
version: VersionsNumber,
|
||||
fqdn: String,
|
||||
fqdn_id: String,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: String,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
}
|
||||
|
||||
impl PostgresScw {
|
||||
pub fn new(
|
||||
context: Context,
|
||||
id: &str,
|
||||
action: Action,
|
||||
name: &str,
|
||||
version: VersionsNumber,
|
||||
fqdn: &str,
|
||||
fqdn_id: &str,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: &str,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
) -> Self {
|
||||
Self {
|
||||
context,
|
||||
action,
|
||||
id: id.to_string(),
|
||||
name: name.to_string(),
|
||||
version,
|
||||
fqdn: fqdn.to_string(),
|
||||
fqdn_id: fqdn_id.to_string(),
|
||||
total_cpus,
|
||||
total_ram_in_mib,
|
||||
database_instance_type: database_instance_type.to_string(),
|
||||
options,
|
||||
listeners,
|
||||
logger,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
self.logger(),
|
||||
)
|
||||
}
|
||||
|
||||
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 {
|
||||
get_self_hosted_postgres_version(requested_version)
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// {"name":"PostgreSQL","version":"13","end_of_life":"2025-11-13T00:00:00Z"}
|
||||
// {"name":"PostgreSQL","version":"12","end_of_life":"2024-11-14T00:00:00Z"}
|
||||
// {"name":"PostgreSQL","version":"11","end_of_life":"2023-11-09T00:00:00Z"}
|
||||
// {"name":"PostgreSQL","version":"10","end_of_life":"2022-11-10T00:00:00Z"}
|
||||
supported_postgres_versions.insert("10".to_string(), "10".to_string());
|
||||
supported_postgres_versions.insert("10.0".to_string(), "10.0".to_string());
|
||||
supported_postgres_versions.insert("11".to_string(), "11".to_string());
|
||||
supported_postgres_versions.insert("11.0".to_string(), "11.0".to_string());
|
||||
supported_postgres_versions.insert("12".to_string(), "12".to_string());
|
||||
supported_postgres_versions.insert("12.0".to_string(), "12.0".to_string());
|
||||
supported_postgres_versions.insert("13".to_string(), "13".to_string());
|
||||
supported_postgres_versions.insert("13.0".to_string(), "13.0".to_string());
|
||||
|
||||
get_supported_version_to_use("RDB postgres", supported_postgres_versions, requested_version)
|
||||
}
|
||||
|
||||
fn cloud_provider_name(&self) -> &str {
|
||||
"scaleway"
|
||||
}
|
||||
|
||||
fn struct_name(&self) -> &str {
|
||||
"postgresql"
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulService for PostgresScw {
|
||||
fn as_stateful_service(&self) -> &dyn StatefulService {
|
||||
self
|
||||
}
|
||||
|
||||
fn is_managed_service(&self) -> bool {
|
||||
self.options.mode == MANAGED
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTransmitter for PostgresScw {
|
||||
fn to_transmitter(&self) -> Transmitter {
|
||||
Transmitter::Database(self.id().to_string(), self.service_type().to_string(), self.name().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Service for PostgresScw {
|
||||
fn context(&self) -> &Context {
|
||||
&self.context
|
||||
}
|
||||
|
||||
fn service_type(&self) -> ServiceType {
|
||||
ServiceType::Database(DatabaseType::PostgreSQL(&self.options))
|
||||
}
|
||||
|
||||
fn id(&self) -> &str {
|
||||
self.id.as_str()
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
fn sanitized_name(&self) -> String {
|
||||
sanitize_name("postgresql", self.name())
|
||||
}
|
||||
|
||||
fn version(&self) -> String {
|
||||
self.version.to_string()
|
||||
}
|
||||
|
||||
fn action(&self) -> &Action {
|
||||
&self.action
|
||||
}
|
||||
|
||||
fn private_port(&self) -> Option<u16> {
|
||||
Some(self.options.port)
|
||||
}
|
||||
|
||||
fn start_timeout(&self) -> Timeout<u32> {
|
||||
Timeout::Value(600)
|
||||
}
|
||||
|
||||
fn total_cpus(&self) -> String {
|
||||
self.total_cpus.to_string()
|
||||
}
|
||||
|
||||
fn cpu_burst(&self) -> String {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn total_ram_in_mib(&self) -> u32 {
|
||||
self.total_ram_in_mib
|
||||
}
|
||||
|
||||
fn min_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn max_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn publicly_accessible(&self) -> bool {
|
||||
self.options.publicly_accessible
|
||||
}
|
||||
|
||||
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 = kubernetes.get_kubeconfig_file_path()?;
|
||||
context.insert("kubeconfig_path", &kube_config_file_path);
|
||||
|
||||
kubectl::kubectl_exec_create_namespace_without_labels(
|
||||
environment.namespace(),
|
||||
kube_config_file_path.as_str(),
|
||||
kubernetes.cloud_provider().credentials_environment_variables(),
|
||||
);
|
||||
|
||||
context.insert("namespace", environment.namespace());
|
||||
|
||||
let version = &self
|
||||
.matching_correct_version(self.is_managed_service(), event_details)?
|
||||
.matched_version();
|
||||
context.insert("version_major", &version.to_major_version_string());
|
||||
context.insert("version", &version.to_string()); // Scaleway needs to have major version only
|
||||
|
||||
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
|
||||
context.insert(k, v);
|
||||
}
|
||||
|
||||
context.insert("kubernetes_cluster_id", kubernetes.id());
|
||||
context.insert("kubernetes_cluster_name", kubernetes.name());
|
||||
|
||||
context.insert("fqdn_id", self.fqdn_id.as_str());
|
||||
context.insert("fqdn", self.fqdn(target, &self.fqdn, self.is_managed_service()).as_str());
|
||||
context.insert("service_name", self.fqdn_id.as_str());
|
||||
context.insert("database_name", self.sanitized_name().as_str());
|
||||
context.insert("database_db_name", self.name());
|
||||
context.insert("database_login", self.options.login.as_str());
|
||||
context.insert("database_password", self.options.password.as_str());
|
||||
context.insert("database_port", &self.private_port());
|
||||
context.insert("database_disk_size_in_gib", &self.options.disk_size_in_gib);
|
||||
context.insert("database_instance_type", &self.database_instance_type);
|
||||
context.insert("database_disk_type", &self.options.database_disk_type);
|
||||
context.insert("database_ram_size_in_mib", &self.total_ram_in_mib);
|
||||
context.insert("database_total_cpus", &self.total_cpus);
|
||||
context.insert("database_fqdn", &self.options.host.as_str());
|
||||
context.insert("database_id", &self.id());
|
||||
context.insert("tfstate_suffix_name", &get_tfstate_suffix(self));
|
||||
context.insert("tfstate_name", &get_tfstate_name(self));
|
||||
|
||||
context.insert("publicly_accessible", &self.options.publicly_accessible);
|
||||
context.insert("activate_high_availability", &self.options.activate_high_availability);
|
||||
context.insert("activate_backups", &self.options.activate_backups);
|
||||
context.insert("delete_automated_backups", &self.context().is_test_cluster());
|
||||
context.insert("skip_final_snapshot", &self.context().is_test_cluster());
|
||||
if self.context.resource_expiration_in_seconds().is_some() {
|
||||
context.insert("resource_expiration_in_seconds", &self.context.resource_expiration_in_seconds())
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
fn logger(&self) -> &dyn Logger {
|
||||
&*self.logger
|
||||
}
|
||||
|
||||
fn selector(&self) -> Option<String> {
|
||||
Some(format!("app={}", self.sanitized_name()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Database for PostgresScw {}
|
||||
|
||||
impl Helm for PostgresScw {
|
||||
fn helm_selector(&self) -> Option<String> {
|
||||
self.selector()
|
||||
}
|
||||
|
||||
fn helm_release_name(&self) -> String {
|
||||
crate::string::cut(format!("postgresql-{}", self.id()), 50)
|
||||
}
|
||||
|
||||
fn helm_chart_dir(&self) -> String {
|
||||
format!("{}/common/services/postgresql", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_values_dir(&self) -> String {
|
||||
format!("{}/scaleway/chart_values/postgresql", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_external_name_service_dir(&self) -> String {
|
||||
format!("{}/common/charts/external-name-svc", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Terraform for PostgresScw {
|
||||
fn terraform_common_resource_dir_path(&self) -> String {
|
||||
format!("{}/scaleway/services/common", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn terraform_resource_dir_path(&self) -> String {
|
||||
format!("{}/scaleway/services/postgresql", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Create for PostgresScw {
|
||||
#[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_stateful_service(target, self, event_details.clone(), self.logger())
|
||||
})
|
||||
}
|
||||
|
||||
fn on_create_check(&self) -> Result<(), EngineError> {
|
||||
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
|
||||
self.check_domains(self.listeners.clone(), vec![self.fqdn.as_str()], event_details, self.logger())
|
||||
}
|
||||
|
||||
#[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 PostgresScw {
|
||||
#[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, || {
|
||||
scale_down_database(target, self, 0)
|
||||
})
|
||||
}
|
||||
|
||||
fn on_pause_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 PostgresScw {
|
||||
#[named]
|
||||
fn on_delete(&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.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())
|
||||
})
|
||||
}
|
||||
|
||||
fn on_delete_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 PostgresScw {
|
||||
fn listeners(&self) -> &Listeners {
|
||||
&self.listeners
|
||||
}
|
||||
|
||||
fn add_listener(&mut self, listener: Listener) {
|
||||
self.listeners.push(listener);
|
||||
}
|
||||
}
|
||||
@@ -1,382 +0,0 @@
|
||||
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, 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::errors::EngineError;
|
||||
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter};
|
||||
use crate::io_models::DatabaseMode::MANAGED;
|
||||
use crate::io_models::{Context, Listen, Listener, Listeners};
|
||||
use crate::logger::Logger;
|
||||
use ::function_name::named;
|
||||
|
||||
pub struct RedisScw {
|
||||
context: Context,
|
||||
id: String,
|
||||
action: Action,
|
||||
name: String,
|
||||
version: String,
|
||||
fqdn: String,
|
||||
fqdn_id: String,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: String,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
}
|
||||
|
||||
impl RedisScw {
|
||||
pub fn new(
|
||||
context: Context,
|
||||
id: &str,
|
||||
action: Action,
|
||||
name: &str,
|
||||
version: &str,
|
||||
fqdn: &str,
|
||||
fqdn_id: &str,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: &str,
|
||||
options: DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
) -> Self {
|
||||
Self {
|
||||
context,
|
||||
action,
|
||||
id: id.to_string(),
|
||||
name: name.to_string(),
|
||||
version: version.to_string(),
|
||||
fqdn: fqdn.to_string(),
|
||||
fqdn_id: fqdn_id.to_string(),
|
||||
total_cpus,
|
||||
total_ram_in_mib,
|
||||
database_instance_type: database_instance_type.to_string(),
|
||||
options,
|
||||
listeners,
|
||||
logger,
|
||||
}
|
||||
}
|
||||
|
||||
fn matching_correct_version(&self, event_details: EventDetails) -> Result<ServiceVersionCheckResult, EngineError> {
|
||||
check_service_version(
|
||||
get_self_hosted_redis_version(self.version()),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
}
|
||||
|
||||
fn cloud_provider_name(&self) -> &str {
|
||||
"scaleway"
|
||||
}
|
||||
|
||||
fn struct_name(&self) -> &str {
|
||||
"redis"
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulService for RedisScw {
|
||||
fn as_stateful_service(&self) -> &dyn StatefulService {
|
||||
self
|
||||
}
|
||||
|
||||
fn is_managed_service(&self) -> bool {
|
||||
self.options.mode == MANAGED
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTransmitter for RedisScw {
|
||||
fn to_transmitter(&self) -> Transmitter {
|
||||
Transmitter::Database(self.id().to_string(), self.service_type().to_string(), self.name().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Service for RedisScw {
|
||||
fn context(&self) -> &Context {
|
||||
&self.context
|
||||
}
|
||||
|
||||
fn service_type(&self) -> ServiceType {
|
||||
ServiceType::Database(DatabaseType::Redis(&self.options))
|
||||
}
|
||||
|
||||
fn id(&self) -> &str {
|
||||
self.id.as_str()
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
fn sanitized_name(&self) -> String {
|
||||
sanitize_name("redis", self.name())
|
||||
}
|
||||
|
||||
fn version(&self) -> String {
|
||||
self.version.clone()
|
||||
}
|
||||
|
||||
fn action(&self) -> &Action {
|
||||
&self.action
|
||||
}
|
||||
|
||||
fn private_port(&self) -> Option<u16> {
|
||||
Some(self.options.port)
|
||||
}
|
||||
|
||||
fn start_timeout(&self) -> Timeout<u32> {
|
||||
Timeout::Value(600)
|
||||
}
|
||||
|
||||
fn total_cpus(&self) -> String {
|
||||
self.total_cpus.to_string()
|
||||
}
|
||||
|
||||
fn cpu_burst(&self) -> String {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn total_ram_in_mib(&self) -> u32 {
|
||||
self.total_ram_in_mib
|
||||
}
|
||||
|
||||
fn min_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn max_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn publicly_accessible(&self) -> bool {
|
||||
self.options.publicly_accessible
|
||||
}
|
||||
|
||||
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 = kubernetes.get_kubeconfig_file_path()?;
|
||||
context.insert("kubeconfig_path", &kube_config_file_path);
|
||||
|
||||
kubectl::kubectl_exec_create_namespace_without_labels(
|
||||
environment.namespace(),
|
||||
kube_config_file_path.as_str(),
|
||||
kubernetes.cloud_provider().credentials_environment_variables(),
|
||||
);
|
||||
|
||||
let version = self
|
||||
.matching_correct_version(event_details)?
|
||||
.matched_version()
|
||||
.to_string();
|
||||
|
||||
context.insert("namespace", environment.namespace());
|
||||
context.insert("version", &version);
|
||||
|
||||
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
|
||||
context.insert(k, v);
|
||||
}
|
||||
|
||||
context.insert("kubernetes_cluster_id", kubernetes.id());
|
||||
context.insert("kubernetes_cluster_name", kubernetes.name());
|
||||
|
||||
context.insert("fqdn_id", self.fqdn_id.as_str());
|
||||
context.insert("fqdn", self.fqdn(target, &self.fqdn, self.is_managed_service()).as_str());
|
||||
context.insert("service_name", self.fqdn_id.as_str());
|
||||
context.insert("database_login", self.options.login.as_str());
|
||||
context.insert("database_password", self.options.password.as_str());
|
||||
context.insert("database_port", &self.private_port());
|
||||
context.insert("database_disk_size_in_gib", &self.options.disk_size_in_gib);
|
||||
context.insert("database_instance_type", &self.database_instance_type);
|
||||
context.insert("database_disk_type", &self.options.database_disk_type);
|
||||
context.insert("database_ram_size_in_mib", &self.total_ram_in_mib);
|
||||
context.insert("database_total_cpus", &self.total_cpus);
|
||||
context.insert("database_fqdn", &self.options.host.as_str());
|
||||
context.insert("database_id", &self.id());
|
||||
context.insert("tfstate_suffix_name", &get_tfstate_suffix(self));
|
||||
context.insert("tfstate_name", &get_tfstate_name(self));
|
||||
context.insert("publicly_accessible", &self.options.publicly_accessible);
|
||||
|
||||
if self.context.resource_expiration_in_seconds().is_some() {
|
||||
context.insert("resource_expiration_in_seconds", &self.context.resource_expiration_in_seconds())
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
fn logger(&self) -> &dyn Logger {
|
||||
&*self.logger
|
||||
}
|
||||
|
||||
fn selector(&self) -> Option<String> {
|
||||
Some(format!("app={}", self.sanitized_name()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Database for RedisScw {}
|
||||
|
||||
impl Helm for RedisScw {
|
||||
fn helm_selector(&self) -> Option<String> {
|
||||
self.selector()
|
||||
}
|
||||
|
||||
fn helm_release_name(&self) -> String {
|
||||
crate::string::cut(format!("redis-{}", self.id()), 50)
|
||||
}
|
||||
|
||||
fn helm_chart_dir(&self) -> String {
|
||||
format!("{}/common/services/redis", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_values_dir(&self) -> String {
|
||||
format!("{}/scaleway/chart_values/redis", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn helm_chart_external_name_service_dir(&self) -> String {
|
||||
format!("{}/common/charts/external-name-svc", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Terraform for RedisScw {
|
||||
fn terraform_common_resource_dir_path(&self) -> String {
|
||||
format!("{}/scaleway/services/common", self.context.lib_root_dir())
|
||||
}
|
||||
|
||||
fn terraform_resource_dir_path(&self) -> String {
|
||||
format!("{}/scaleway/services/redis", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl Create for RedisScw {
|
||||
#[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_stateful_service(target, self, event_details.clone(), self.logger())
|
||||
})
|
||||
}
|
||||
|
||||
fn on_create_check(&self) -> Result<(), EngineError> {
|
||||
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
|
||||
self.check_domains(self.listeners.clone(), vec![self.fqdn.as_str()], event_details, self.logger())
|
||||
}
|
||||
|
||||
#[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 RedisScw {
|
||||
#[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, || {
|
||||
scale_down_database(target, self, 0)
|
||||
})
|
||||
}
|
||||
|
||||
fn on_pause_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 RedisScw {
|
||||
#[named]
|
||||
fn on_delete(&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.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())
|
||||
})
|
||||
}
|
||||
|
||||
fn on_delete_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 RedisScw {
|
||||
fn listeners(&self) -> &Listeners {
|
||||
&self.listeners
|
||||
}
|
||||
|
||||
fn add_listener(&mut self, listener: Listener) {
|
||||
self.listeners.push(listener);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ use crate::constants::{SCALEWAY_ACCESS_KEY, SCALEWAY_DEFAULT_PROJECT_ID, SCALEWA
|
||||
use crate::events::{EventDetails, Stage, ToTransmitter, Transmitter};
|
||||
use crate::io_models::{Context, Listen, Listener, Listeners, QoveryIdentifier};
|
||||
|
||||
pub mod databases;
|
||||
pub mod kubernetes;
|
||||
|
||||
pub struct Scaleway {
|
||||
|
||||
@@ -10,7 +10,7 @@ use tera::Context as TeraContext;
|
||||
use crate::cloud_provider::environment::Environment;
|
||||
use crate::cloud_provider::helm::ChartInfo;
|
||||
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;
|
||||
use crate::cmd::helm;
|
||||
@@ -26,6 +26,7 @@ use crate::io_models::{
|
||||
QoveryIdentifier,
|
||||
};
|
||||
use crate::logger::Logger;
|
||||
use crate::models::types::VersionsNumber;
|
||||
|
||||
pub trait Service: ToTransmitter {
|
||||
fn context(&self) -> &Context;
|
||||
@@ -250,32 +251,32 @@ pub struct DatabaseOptions {
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub enum DatabaseType<'a> {
|
||||
PostgreSQL(&'a DatabaseOptions),
|
||||
MongoDB(&'a DatabaseOptions),
|
||||
MySQL(&'a DatabaseOptions),
|
||||
Redis(&'a DatabaseOptions),
|
||||
pub enum DatabaseType {
|
||||
PostgreSQL,
|
||||
MongoDB,
|
||||
MySQL,
|
||||
Redis,
|
||||
}
|
||||
|
||||
impl<'a> ToString for DatabaseType<'a> {
|
||||
impl ToString for DatabaseType {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
DatabaseType::PostgreSQL(_) => "PostgreSQL".to_string(),
|
||||
DatabaseType::MongoDB(_) => "MongoDB".to_string(),
|
||||
DatabaseType::MySQL(_) => "MySQL".to_string(),
|
||||
DatabaseType::Redis(_) => "Redis".to_string(),
|
||||
DatabaseType::PostgreSQL => "PostgreSQL".to_string(),
|
||||
DatabaseType::MongoDB => "MongoDB".to_string(),
|
||||
DatabaseType::MySQL => "MySQL".to_string(),
|
||||
DatabaseType::Redis => "Redis".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub enum ServiceType<'a> {
|
||||
pub enum ServiceType {
|
||||
Application,
|
||||
Database(DatabaseType<'a>),
|
||||
Database(DatabaseType),
|
||||
Router,
|
||||
}
|
||||
|
||||
impl<'a> ServiceType<'a> {
|
||||
impl ServiceType {
|
||||
pub fn name(&self) -> String {
|
||||
match self {
|
||||
ServiceType::Application => "Application".to_string(),
|
||||
@@ -285,7 +286,7 @@ impl<'a> ServiceType<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToString for ServiceType<'a> {
|
||||
impl<'a> ToString for ServiceType {
|
||||
fn to_string(&self) -> String {
|
||||
self.name()
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#![allow(clippy::field_reassign_with_default)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::errors::{CommandError, EngineError};
|
||||
use crate::errors::EngineError;
|
||||
use crate::events::{EngineEvent, EventDetails, EventMessage};
|
||||
use crate::io_models::{Listeners, ListenersHelper, ProgressInfo, ProgressLevel, ProgressScope};
|
||||
use crate::logger::Logger;
|
||||
@@ -12,306 +10,10 @@ use core::result::Result;
|
||||
use core::result::Result::{Err, Ok};
|
||||
use retry::delay::Fixed;
|
||||
use retry::OperationResult;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::fmt::Write;
|
||||
use std::str::FromStr;
|
||||
use trust_dns_resolver::config::*;
|
||||
use trust_dns_resolver::proto::rr::{RData, RecordType};
|
||||
use trust_dns_resolver::Resolver;
|
||||
|
||||
pub fn get_self_hosted_postgres_version(requested_version: String) -> Result<String, CommandError> {
|
||||
let mut supported_postgres_versions = HashMap::new();
|
||||
|
||||
// https://hub.docker.com/r/bitnami/postgresql/tags?page=1&ordering=last_updated
|
||||
|
||||
// v10
|
||||
let v10 = generate_supported_version(10, 1, 16, Some(0), Some(0), None);
|
||||
supported_postgres_versions.extend(v10);
|
||||
|
||||
// v11
|
||||
let v11 = generate_supported_version(11, 1, 11, Some(0), Some(0), None);
|
||||
supported_postgres_versions.extend(v11);
|
||||
|
||||
// v12
|
||||
let v12 = generate_supported_version(12, 2, 8, Some(0), Some(0), None);
|
||||
supported_postgres_versions.extend(v12);
|
||||
|
||||
// v13
|
||||
let v13 = generate_supported_version(13, 1, 4, Some(0), Some(0), None);
|
||||
supported_postgres_versions.extend(v13);
|
||||
|
||||
get_supported_version_to_use("Postgresql", supported_postgres_versions, requested_version)
|
||||
}
|
||||
|
||||
pub fn get_self_hosted_mysql_version(requested_version: String) -> Result<String, CommandError> {
|
||||
let mut supported_mysql_versions = HashMap::new();
|
||||
// https://hub.docker.com/r/bitnami/mysql/tags?page=1&ordering=last_updated
|
||||
|
||||
// v5.7
|
||||
let v57 = generate_supported_version(5, 7, 7, Some(16), Some(34), None);
|
||||
supported_mysql_versions.extend(v57);
|
||||
|
||||
// v8
|
||||
let v8 = generate_supported_version(8, 0, 0, Some(11), Some(24), None);
|
||||
supported_mysql_versions.extend(v8);
|
||||
|
||||
get_supported_version_to_use("MySQL", supported_mysql_versions, requested_version)
|
||||
}
|
||||
|
||||
pub fn get_self_hosted_mongodb_version(requested_version: String) -> Result<String, CommandError> {
|
||||
let mut supported_mongodb_versions = HashMap::new();
|
||||
|
||||
// https://hub.docker.com/r/bitnami/mongodb/tags?page=1&ordering=last_updated
|
||||
|
||||
// v3.6
|
||||
let mongo_version = generate_supported_version(3, 6, 6, Some(0), Some(22), None);
|
||||
supported_mongodb_versions.extend(mongo_version);
|
||||
|
||||
// v4.0
|
||||
let mongo_version = generate_supported_version(4, 0, 0, Some(0), Some(23), None);
|
||||
supported_mongodb_versions.extend(mongo_version);
|
||||
|
||||
// v4.2
|
||||
let mongo_version = generate_supported_version(4, 2, 2, Some(0), Some(12), None);
|
||||
supported_mongodb_versions.extend(mongo_version);
|
||||
|
||||
// v4.4
|
||||
let mongo_version = generate_supported_version(4, 4, 4, Some(0), Some(4), None);
|
||||
supported_mongodb_versions.extend(mongo_version);
|
||||
|
||||
get_supported_version_to_use("MongoDB", supported_mongodb_versions, requested_version)
|
||||
}
|
||||
|
||||
pub fn get_self_hosted_redis_version(requested_version: String) -> Result<String, CommandError> {
|
||||
let mut supported_redis_versions = HashMap::with_capacity(4);
|
||||
// https://hub.docker.com/r/bitnami/redis/tags?page=1&ordering=last_updated
|
||||
|
||||
supported_redis_versions.insert("6".to_string(), "6.0.9".to_string());
|
||||
supported_redis_versions.insert("6.0".to_string(), "6.0.9".to_string());
|
||||
supported_redis_versions.insert("5".to_string(), "5.0.10".to_string());
|
||||
supported_redis_versions.insert("5.0".to_string(), "5.0.10".to_string());
|
||||
|
||||
get_supported_version_to_use("Redis", supported_redis_versions, requested_version)
|
||||
}
|
||||
|
||||
pub fn get_supported_version_to_use(
|
||||
database_name: &str,
|
||||
all_supported_versions: HashMap<String, String>,
|
||||
version_to_check: String,
|
||||
) -> Result<String, CommandError> {
|
||||
let version = VersionsNumber::from_str(version_to_check.as_str())?;
|
||||
|
||||
// if a patch version is required
|
||||
if version.patch.is_some() {
|
||||
return match all_supported_versions.get(&format!(
|
||||
"{}.{}.{}",
|
||||
version.major,
|
||||
version.minor.unwrap(),
|
||||
version.patch.unwrap()
|
||||
)) {
|
||||
Some(version) => Ok(version.to_string()),
|
||||
None => {
|
||||
return Err(CommandError::new_from_safe_message(format!(
|
||||
"{} {} version is not supported",
|
||||
database_name, version_to_check
|
||||
)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// if a minor version is required
|
||||
if version.minor.is_some() {
|
||||
return match all_supported_versions.get(&format!("{}.{}", version.major, version.minor.unwrap())) {
|
||||
Some(version) => Ok(version.to_string()),
|
||||
None => {
|
||||
return Err(CommandError::new_from_safe_message(format!(
|
||||
"{} {} version is not supported",
|
||||
database_name, version_to_check
|
||||
)));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// if only a major version is required
|
||||
match all_supported_versions.get(&version.major) {
|
||||
Some(version) => Ok(version.to_string()),
|
||||
None => {
|
||||
return Err(CommandError::new_from_safe_message(format!(
|
||||
"{} {} version is not supported",
|
||||
database_name, version_to_check
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ease the support of multiple versions by range
|
||||
pub fn generate_supported_version(
|
||||
major: i32,
|
||||
minor_min: i32,
|
||||
minor_max: i32,
|
||||
update_min: Option<i32>,
|
||||
update_max: Option<i32>,
|
||||
suffix_version: Option<String>,
|
||||
) -> HashMap<String, String> {
|
||||
let mut supported_versions = HashMap::new();
|
||||
let latest_major_version;
|
||||
|
||||
// blank suffix if not requested
|
||||
let suffix = match suffix_version {
|
||||
Some(suffix) => suffix,
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
let _ = match update_min {
|
||||
// manage minor with updates
|
||||
Some(_) => {
|
||||
latest_major_version = format!("{}.{}.{}{}", major, minor_max, update_max.unwrap(), suffix);
|
||||
|
||||
if minor_min == minor_max {
|
||||
// add short minor format targeting latest version
|
||||
supported_versions.insert(format!("{}.{}", major, minor_max), latest_major_version.clone());
|
||||
if update_min.unwrap() == update_max.unwrap() {
|
||||
let version = format!("{}.{}.{}", major, minor_min, update_min.unwrap());
|
||||
supported_versions.insert(version.clone(), format!("{}{}", version, suffix));
|
||||
} else {
|
||||
for update in update_min.unwrap()..update_max.unwrap() + 1 {
|
||||
let version = format!("{}.{}.{}", major, minor_min, update);
|
||||
supported_versions.insert(version.clone(), format!("{}{}", version, suffix));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for minor in minor_min..minor_max + 1 {
|
||||
// add short minor format targeting latest version
|
||||
supported_versions.insert(
|
||||
format!("{}.{}", major, minor),
|
||||
format!("{}.{}.{}", major, minor, update_max.unwrap()),
|
||||
);
|
||||
if update_min.unwrap() == update_max.unwrap() {
|
||||
let version = format!("{}.{}.{}", major, minor, update_min.unwrap());
|
||||
supported_versions.insert(version.clone(), format!("{}{}", version, suffix));
|
||||
} else {
|
||||
for update in update_min.unwrap()..update_max.unwrap() + 1 {
|
||||
let version = format!("{}.{}.{}", major, minor, update);
|
||||
supported_versions.insert(version.clone(), format!("{}{}", version, suffix));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// manage minor without updates
|
||||
None => {
|
||||
latest_major_version = format!("{}.{}{}", major, minor_max, suffix);
|
||||
for minor in minor_min..minor_max + 1 {
|
||||
let version = format!("{}.{}", major, minor);
|
||||
supported_versions.insert(version.clone(), format!("{}{}", version, suffix));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// default major + major.minor supported version
|
||||
supported_versions.insert(major.to_string(), latest_major_version);
|
||||
|
||||
supported_versions
|
||||
}
|
||||
|
||||
// unfortunately some proposed versions are not SemVer like Elasticache (6.x)
|
||||
// this is why we need ot have our own structure
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub struct VersionsNumber {
|
||||
pub(crate) major: String,
|
||||
pub(crate) minor: Option<String>,
|
||||
pub(crate) patch: Option<String>,
|
||||
pub(crate) suffix: Option<String>,
|
||||
}
|
||||
|
||||
impl VersionsNumber {
|
||||
pub fn new(major: String, minor: Option<String>, patch: Option<String>, suffix: Option<String>) -> Self {
|
||||
VersionsNumber {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
suffix,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_major_version_string(&self) -> String {
|
||||
self.major.clone()
|
||||
}
|
||||
|
||||
pub fn to_major_minor_version_string(&self, default_minor: &str) -> String {
|
||||
let test = format!(
|
||||
"{}.{}",
|
||||
self.major.clone(),
|
||||
self.minor.as_ref().unwrap_or(&default_minor.to_string())
|
||||
);
|
||||
|
||||
test
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for VersionsNumber {
|
||||
type Err = CommandError;
|
||||
|
||||
fn from_str(version: &str) -> Result<Self, Self::Err> {
|
||||
if version.trim() == "" {
|
||||
return Err(CommandError::new_from_safe_message("version cannot be empty".to_string()));
|
||||
}
|
||||
|
||||
let mut version_split = version.splitn(4, '.').map(|v| v.trim());
|
||||
|
||||
let major = match version_split.next() {
|
||||
Some(major) => {
|
||||
let major = major.to_string();
|
||||
major.replace('v', "")
|
||||
}
|
||||
None => {
|
||||
return Err(CommandError::new_from_safe_message(format!(
|
||||
"please check the version you've sent ({}), it can't be checked",
|
||||
version
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
let minor = version_split.next().map(|minor| {
|
||||
let minor = minor.to_string();
|
||||
minor.replace('+', "")
|
||||
});
|
||||
|
||||
let patch = version_split.next().map(|patch| patch.to_string());
|
||||
|
||||
let suffix = version_split.next().map(|suffix| suffix.to_string());
|
||||
|
||||
// TODO(benjaminch): Handle properly the case where versions are empty
|
||||
// eq. 1..2
|
||||
|
||||
Ok(VersionsNumber::new(major, minor, patch, suffix))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for VersionsNumber {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(&self.major)?;
|
||||
|
||||
if let Some(minor) = &self.minor {
|
||||
f.write_char('.')?;
|
||||
f.write_str(minor)?;
|
||||
}
|
||||
|
||||
if let Some(patch) = &self.patch {
|
||||
f.write_char('.')?;
|
||||
f.write_str(patch)?;
|
||||
}
|
||||
|
||||
if let Some(suffix) = &self.suffix {
|
||||
f.write_char('.')?;
|
||||
f.write_str(suffix)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn dns_resolvers() -> Vec<Resolver> {
|
||||
let mut resolver_options = ResolverOpts::default();
|
||||
|
||||
@@ -559,8 +261,9 @@ pub fn print_action(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::cloud_provider::utilities::{dns_resolvers, get_cname_record_value, VersionsNumber};
|
||||
use crate::cloud_provider::utilities::{dns_resolvers, get_cname_record_value};
|
||||
use crate::errors::CommandError;
|
||||
use crate::models::types::VersionsNumber;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -3,7 +3,6 @@ pub mod io;
|
||||
extern crate url;
|
||||
|
||||
use crate::build_platform::BuildError;
|
||||
use crate::cloud_provider::utilities::VersionsNumber;
|
||||
use crate::cmd;
|
||||
use crate::cmd::docker::DockerError;
|
||||
use crate::cmd::helm::HelmError;
|
||||
@@ -11,6 +10,7 @@ use crate::container_registry::errors::ContainerRegistryError;
|
||||
use crate::error::{EngineError as LegacyEngineError, EngineErrorCause, EngineErrorScope};
|
||||
use crate::events::{EventDetails, GeneralStep, Stage, Transmitter};
|
||||
use crate::io_models::QoveryIdentifier;
|
||||
use crate::models::types::VersionsNumber;
|
||||
use crate::object_storage::errors::ObjectStorageError;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use thiserror::Error;
|
||||
|
||||
661
src/io_models.rs
661
src/io_models.rs
@@ -14,34 +14,21 @@ use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use crate::build_platform::{Build, Credentials, GitRepository, Image, SshKey};
|
||||
use crate::cloud_provider::aws::databases::mongodb::MongoDbAws;
|
||||
use crate::cloud_provider::aws::databases::mysql::MySQLAws;
|
||||
use crate::cloud_provider::aws::databases::postgresql::PostgreSQLAws;
|
||||
use crate::cloud_provider::aws::databases::redis::RedisAws;
|
||||
use crate::cloud_provider::digitalocean::databases::mongodb::MongoDo;
|
||||
use crate::cloud_provider::digitalocean::databases::mysql::MySQLDo;
|
||||
use crate::cloud_provider::digitalocean::databases::postgresql::PostgresDo;
|
||||
use crate::cloud_provider::digitalocean::databases::redis::RedisDo;
|
||||
use crate::cloud_provider::environment::Environment;
|
||||
use crate::cloud_provider::scaleway::databases::mongodb::MongoDbScw;
|
||||
use crate::cloud_provider::scaleway::databases::mysql::MySQLScw;
|
||||
use crate::cloud_provider::scaleway::databases::postgresql::PostgresScw;
|
||||
use crate::cloud_provider::scaleway::databases::redis::RedisScw;
|
||||
use crate::cloud_provider::service::{DatabaseOptions, RouterService};
|
||||
use crate::cloud_provider::utilities::VersionsNumber;
|
||||
use crate::cloud_provider::CloudProvider;
|
||||
use crate::cloud_provider::Kind as CPKind;
|
||||
use crate::cmd::docker::Docker;
|
||||
use crate::container_registry::ContainerRegistryInfo;
|
||||
use crate::errors::ErrorMessageVerbosity;
|
||||
use crate::logger::Logger;
|
||||
use crate::models;
|
||||
use crate::models::application::{ApplicationError, ApplicationService};
|
||||
use crate::models::aws::{AwsAppExtraSettings, AwsRouterExtraSettings, AwsStorageType};
|
||||
use crate::models::database::{Container, Managed, MongoDB, MySQL, PostgresSQL, Redis};
|
||||
use crate::models::digital_ocean::{DoAppExtraSettings, DoRouterExtraSettings, DoStorageType};
|
||||
use crate::models::router::RouterError;
|
||||
use crate::models::scaleway::{ScwAppExtraSettings, ScwRouterExtraSettings, ScwStorageType};
|
||||
use crate::models::types::{AWS, DO, SCW};
|
||||
use crate::models::types::{VersionsNumber, AWS, DO, SCW};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct QoveryIdentifier {
|
||||
@@ -651,265 +638,415 @@ impl Database {
|
||||
|
||||
let listeners = cloud_provider.listeners().clone();
|
||||
|
||||
match cloud_provider.kind() {
|
||||
CPKind::Aws => match self.kind {
|
||||
DatabaseKind::Postgresql => {
|
||||
let db = Box::new(PostgreSQLAws::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
self.version.as_str(),
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
));
|
||||
match (cloud_provider.kind(), &self.kind, &self.mode) {
|
||||
(CPKind::Aws, DatabaseKind::Postgresql, DatabaseMode::MANAGED) => {
|
||||
let db = models::database::Database::<AWS, Managed, PostgresSQL>::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
VersionsNumber::from_str(self.version.as_str()).ok()?,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options.publicly_accessible,
|
||||
database_options.port,
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Some(db)
|
||||
}
|
||||
DatabaseKind::Mysql => {
|
||||
let db = Box::new(MySQLAws::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
self.version.as_str(),
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
));
|
||||
Some(Box::new(db))
|
||||
}
|
||||
(CPKind::Aws, DatabaseKind::Postgresql, DatabaseMode::CONTAINER) => {
|
||||
let db = models::database::Database::<AWS, Container, PostgresSQL>::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
VersionsNumber::from_str(self.version.as_str()).ok()?,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options.publicly_accessible,
|
||||
database_options.port,
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Some(db)
|
||||
}
|
||||
DatabaseKind::Mongodb => {
|
||||
let db = Box::new(MongoDbAws::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
self.version.as_str(),
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
));
|
||||
Some(Box::new(db))
|
||||
}
|
||||
|
||||
Some(db)
|
||||
}
|
||||
DatabaseKind::Redis => {
|
||||
let db = Box::new(RedisAws::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
self.version.as_str(),
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
));
|
||||
(CPKind::Aws, DatabaseKind::Mysql, DatabaseMode::MANAGED) => {
|
||||
let db = models::database::Database::<AWS, Managed, MySQL>::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
VersionsNumber::from_str(self.version.as_str()).ok()?,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options.publicly_accessible,
|
||||
database_options.port,
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Some(db)
|
||||
}
|
||||
},
|
||||
CPKind::Do => match self.kind {
|
||||
DatabaseKind::Postgresql => {
|
||||
let db = Box::new(PostgresDo::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
self.version.as_str(),
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
));
|
||||
Some(Box::new(db))
|
||||
}
|
||||
(CPKind::Aws, DatabaseKind::Mysql, DatabaseMode::CONTAINER) => {
|
||||
let db = models::database::Database::<AWS, Container, MySQL>::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
VersionsNumber::from_str(self.version.as_str()).ok()?,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options.publicly_accessible,
|
||||
database_options.port,
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Some(db)
|
||||
}
|
||||
DatabaseKind::Mysql => {
|
||||
let db = Box::new(MySQLDo::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
self.version.as_str(),
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
));
|
||||
Some(Box::new(db))
|
||||
}
|
||||
(CPKind::Aws, DatabaseKind::Redis, DatabaseMode::MANAGED) => {
|
||||
let db = models::database::Database::<AWS, Managed, Redis>::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
VersionsNumber::from_str(self.version.as_str()).ok()?,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options.publicly_accessible,
|
||||
database_options.port,
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Some(db)
|
||||
}
|
||||
DatabaseKind::Redis => {
|
||||
let db = Box::new(RedisDo::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
self.version.as_str(),
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
));
|
||||
Some(Box::new(db))
|
||||
}
|
||||
(CPKind::Aws, DatabaseKind::Redis, DatabaseMode::CONTAINER) => {
|
||||
let db = models::database::Database::<AWS, Container, Redis>::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
VersionsNumber::from_str(self.version.as_str()).ok()?,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options.publicly_accessible,
|
||||
database_options.port,
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Some(db)
|
||||
}
|
||||
DatabaseKind::Mongodb => {
|
||||
let db = Box::new(MongoDo::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
self.version.as_str(),
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
));
|
||||
Some(Box::new(db))
|
||||
}
|
||||
(CPKind::Aws, DatabaseKind::Mongodb, DatabaseMode::MANAGED) => {
|
||||
let db = models::database::Database::<AWS, Managed, MongoDB>::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
VersionsNumber::from_str(self.version.as_str()).ok()?,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options.publicly_accessible,
|
||||
database_options.port,
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Some(db)
|
||||
}
|
||||
},
|
||||
CPKind::Scw => match self.kind {
|
||||
DatabaseKind::Postgresql => match VersionsNumber::from_str(self.version.as_str()) {
|
||||
Ok(v) => {
|
||||
let db = Box::new(PostgresScw::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
v,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options,
|
||||
listeners,
|
||||
logger.clone(),
|
||||
));
|
||||
Some(Box::new(db))
|
||||
}
|
||||
(CPKind::Aws, DatabaseKind::Mongodb, DatabaseMode::CONTAINER) => {
|
||||
let db = models::database::Database::<AWS, Container, MongoDB>::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
VersionsNumber::from_str(self.version.as_str()).ok()?,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options.publicly_accessible,
|
||||
database_options.port,
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Some(db)
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
"{}",
|
||||
format!(
|
||||
"error while parsing postgres version, error: {}",
|
||||
e.message(ErrorMessageVerbosity::FullDetails)
|
||||
)
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
DatabaseKind::Mysql => match VersionsNumber::from_str(self.version.as_str()) {
|
||||
Ok(v) => {
|
||||
let db = Box::new(MySQLScw::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
v,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options,
|
||||
listeners,
|
||||
logger.clone(),
|
||||
));
|
||||
Some(Box::new(db))
|
||||
}
|
||||
|
||||
Some(db)
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
"{}",
|
||||
format!(
|
||||
"error while parsing mysql version, error: {}",
|
||||
e.message(ErrorMessageVerbosity::FullDetails)
|
||||
)
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
DatabaseKind::Redis => {
|
||||
let db = Box::new(RedisScw::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
self.version.as_str(),
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options,
|
||||
listeners,
|
||||
logger.clone(),
|
||||
));
|
||||
(CPKind::Do, DatabaseKind::Postgresql, DatabaseMode::MANAGED) => None,
|
||||
(CPKind::Do, DatabaseKind::Postgresql, DatabaseMode::CONTAINER) => {
|
||||
let db = models::database::Database::<DO, Container, PostgresSQL>::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
VersionsNumber::from_str(self.version.as_str()).ok()?,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options.publicly_accessible,
|
||||
database_options.port,
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Some(db)
|
||||
}
|
||||
DatabaseKind::Mongodb => {
|
||||
let db = Box::new(MongoDbScw::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
self.version.as_str(),
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
));
|
||||
Some(Box::new(db))
|
||||
}
|
||||
(CPKind::Do, DatabaseKind::Mysql, DatabaseMode::MANAGED) => None,
|
||||
(CPKind::Do, DatabaseKind::Mysql, DatabaseMode::CONTAINER) => {
|
||||
let db = models::database::Database::<DO, Container, MySQL>::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
VersionsNumber::from_str(self.version.as_str()).ok()?,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options.publicly_accessible,
|
||||
database_options.port,
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Some(db)
|
||||
}
|
||||
},
|
||||
Some(Box::new(db))
|
||||
}
|
||||
(CPKind::Do, DatabaseKind::Redis, DatabaseMode::MANAGED) => None,
|
||||
(CPKind::Do, DatabaseKind::Redis, DatabaseMode::CONTAINER) => {
|
||||
let db = models::database::Database::<DO, Container, Redis>::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
VersionsNumber::from_str(self.version.as_str()).ok()?,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options.publicly_accessible,
|
||||
database_options.port,
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Some(Box::new(db))
|
||||
}
|
||||
(CPKind::Do, DatabaseKind::Mongodb, DatabaseMode::MANAGED) => None,
|
||||
(CPKind::Do, DatabaseKind::Mongodb, DatabaseMode::CONTAINER) => {
|
||||
let db = models::database::Database::<DO, Container, MongoDB>::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
VersionsNumber::from_str(self.version.as_str()).ok()?,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options.publicly_accessible,
|
||||
database_options.port,
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Some(Box::new(db))
|
||||
}
|
||||
|
||||
(CPKind::Scw, DatabaseKind::Postgresql, DatabaseMode::MANAGED) => {
|
||||
let db = models::database::Database::<SCW, Managed, PostgresSQL>::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
VersionsNumber::from_str(self.version.as_str()).ok()?,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options.publicly_accessible,
|
||||
database_options.port,
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
)
|
||||
.unwrap();
|
||||
Some(Box::new(db))
|
||||
}
|
||||
(CPKind::Scw, DatabaseKind::Postgresql, DatabaseMode::CONTAINER) => {
|
||||
let db = models::database::Database::<SCW, Container, PostgresSQL>::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
VersionsNumber::from_str(self.version.as_str()).ok()?,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options.publicly_accessible,
|
||||
database_options.port,
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
)
|
||||
.unwrap();
|
||||
Some(Box::new(db))
|
||||
}
|
||||
(CPKind::Scw, DatabaseKind::Mysql, DatabaseMode::MANAGED) => {
|
||||
let db = models::database::Database::<SCW, Managed, MySQL>::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
VersionsNumber::from_str(self.version.as_str()).ok()?,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options.publicly_accessible,
|
||||
database_options.port,
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
)
|
||||
.unwrap();
|
||||
Some(Box::new(db))
|
||||
}
|
||||
(CPKind::Scw, DatabaseKind::Mysql, DatabaseMode::CONTAINER) => {
|
||||
let db = models::database::Database::<SCW, Container, MySQL>::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
VersionsNumber::from_str(self.version.as_str()).ok()?,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options.publicly_accessible,
|
||||
database_options.port,
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Some(Box::new(db))
|
||||
}
|
||||
(CPKind::Scw, DatabaseKind::Redis, DatabaseMode::MANAGED) => {
|
||||
// Not Implemented
|
||||
None
|
||||
}
|
||||
(CPKind::Scw, DatabaseKind::Redis, DatabaseMode::CONTAINER) => {
|
||||
let db = models::database::Database::<SCW, Container, Redis>::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
VersionsNumber::from_str(self.version.as_str()).ok()?,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options.publicly_accessible,
|
||||
database_options.port,
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Some(Box::new(db))
|
||||
}
|
||||
(CPKind::Scw, DatabaseKind::Mongodb, DatabaseMode::MANAGED) => {
|
||||
// Not Implemented
|
||||
None
|
||||
}
|
||||
(CPKind::Scw, DatabaseKind::Mongodb, DatabaseMode::CONTAINER) => {
|
||||
let db = models::database::Database::<SCW, Container, MongoDB>::new(
|
||||
context.clone(),
|
||||
self.id.as_str(),
|
||||
self.action.to_service_action(),
|
||||
self.name.as_str(),
|
||||
VersionsNumber::from_str(self.version.as_str()).ok()?,
|
||||
self.fqdn.as_str(),
|
||||
self.fqdn_id.as_str(),
|
||||
self.total_cpus.clone(),
|
||||
self.total_ram_in_mib,
|
||||
self.database_instance_type.as_str(),
|
||||
database_options.publicly_accessible,
|
||||
database_options.port,
|
||||
database_options,
|
||||
listeners,
|
||||
logger,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Some(Box::new(db))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,23 +26,23 @@ pub enum ApplicationError {
|
||||
|
||||
pub struct Application<T: CloudProvider> {
|
||||
_marker: PhantomData<T>,
|
||||
pub(crate) context: Context,
|
||||
pub(crate) id: String,
|
||||
pub(crate) action: Action,
|
||||
pub(crate) name: String,
|
||||
pub(crate) ports: Vec<Port>,
|
||||
pub(crate) total_cpus: String,
|
||||
pub(crate) cpu_burst: String,
|
||||
pub(crate) total_ram_in_mib: u32,
|
||||
pub(crate) min_instances: u32,
|
||||
pub(crate) max_instances: u32,
|
||||
pub(crate) start_timeout_in_seconds: u32,
|
||||
pub(crate) build: Build,
|
||||
pub(crate) storage: Vec<Storage<T::StorageTypes>>,
|
||||
pub(crate) environment_variables: Vec<EnvironmentVariable>,
|
||||
pub(crate) listeners: Listeners,
|
||||
pub(crate) logger: Box<dyn Logger>,
|
||||
pub(crate) _extra_settings: T::AppExtraSettings,
|
||||
pub(super) context: Context,
|
||||
pub(super) id: String,
|
||||
pub(super) action: Action,
|
||||
pub(super) name: String,
|
||||
pub(super) ports: Vec<Port>,
|
||||
pub(super) total_cpus: String,
|
||||
pub(super) cpu_burst: String,
|
||||
pub(super) total_ram_in_mib: u32,
|
||||
pub(super) min_instances: u32,
|
||||
pub(super) max_instances: u32,
|
||||
pub(super) start_timeout_in_seconds: u32,
|
||||
pub(super) build: Build,
|
||||
pub(super) storage: Vec<Storage<T::StorageTypes>>,
|
||||
pub(super) environment_variables: Vec<EnvironmentVariable>,
|
||||
pub(super) listeners: Listeners,
|
||||
pub(super) logger: Box<dyn Logger>,
|
||||
pub(super) _extra_settings: T::AppExtraSettings,
|
||||
}
|
||||
|
||||
// Here we define the common behavior among all providers
|
||||
@@ -294,7 +294,7 @@ impl<T: CloudProvider> Helm for Application<T> {
|
||||
format!(
|
||||
"{}/{}/charts/q-application",
|
||||
self.context.lib_root_dir(),
|
||||
T::helm_directory_name(),
|
||||
T::lib_directory_name(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
326
src/models/aws/database.rs
Normal file
326
src/models/aws/database.rs
Normal file
@@ -0,0 +1,326 @@
|
||||
use crate::cloud_provider::service::{
|
||||
check_service_version, default_tera_context, get_tfstate_name, get_tfstate_suffix, DatabaseOptions, Service,
|
||||
ServiceVersionCheckResult,
|
||||
};
|
||||
use crate::cloud_provider::{service, DeploymentTarget};
|
||||
use crate::cmd::kubectl;
|
||||
use crate::errors::EngineError;
|
||||
use crate::events::{EnvironmentStep, EventDetails, Stage};
|
||||
use crate::models::aws::database_utils::{
|
||||
get_managed_mongodb_version, get_managed_mysql_version, get_managed_postgres_version, get_managed_redis_version,
|
||||
};
|
||||
use crate::models::database::{
|
||||
Container, Database, DatabaseMode, DatabaseType, Managed, MongoDB, MySQL, PostgresSQL, Redis,
|
||||
};
|
||||
|
||||
use crate::models::types::{ToTeraContext, AWS};
|
||||
use tera::Context as TeraContext;
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// CONTAINER
|
||||
impl DatabaseType<AWS, Container> for PostgresSQL {
|
||||
type DatabaseOptions = DatabaseOptions;
|
||||
|
||||
fn short_name() -> &'static str {
|
||||
"PostgresSQL"
|
||||
}
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"postgresql"
|
||||
}
|
||||
fn db_type() -> service::DatabaseType {
|
||||
service::DatabaseType::PostgreSQL
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseType<AWS, Container> for MySQL {
|
||||
type DatabaseOptions = DatabaseOptions;
|
||||
|
||||
fn short_name() -> &'static str {
|
||||
"MySQL"
|
||||
}
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"mysql"
|
||||
}
|
||||
fn db_type() -> service::DatabaseType {
|
||||
service::DatabaseType::MySQL
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseType<AWS, Container> for Redis {
|
||||
type DatabaseOptions = DatabaseOptions;
|
||||
|
||||
fn short_name() -> &'static str {
|
||||
"Redis"
|
||||
}
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"redis"
|
||||
}
|
||||
|
||||
fn db_type() -> service::DatabaseType {
|
||||
service::DatabaseType::Redis
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseType<AWS, Container> for MongoDB {
|
||||
type DatabaseOptions = DatabaseOptions;
|
||||
|
||||
fn short_name() -> &'static str {
|
||||
"Redis"
|
||||
}
|
||||
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"mongodb"
|
||||
}
|
||||
|
||||
fn db_type() -> service::DatabaseType {
|
||||
service::DatabaseType::MongoDB
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// MANAGED
|
||||
impl DatabaseType<AWS, Managed> for PostgresSQL {
|
||||
type DatabaseOptions = DatabaseOptions;
|
||||
|
||||
fn short_name() -> &'static str {
|
||||
"Postgres RDS"
|
||||
}
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"postgresql"
|
||||
}
|
||||
|
||||
fn db_type() -> service::DatabaseType {
|
||||
service::DatabaseType::PostgreSQL
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseType<AWS, Managed> for MySQL {
|
||||
type DatabaseOptions = DatabaseOptions;
|
||||
|
||||
fn short_name() -> &'static str {
|
||||
"MySQL RDS"
|
||||
}
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"mysql"
|
||||
}
|
||||
|
||||
fn db_type() -> service::DatabaseType {
|
||||
service::DatabaseType::MySQL
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseType<AWS, Managed> for Redis {
|
||||
type DatabaseOptions = DatabaseOptions;
|
||||
|
||||
fn short_name() -> &'static str {
|
||||
"ElasticCache"
|
||||
}
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"redis"
|
||||
}
|
||||
|
||||
fn db_type() -> service::DatabaseType {
|
||||
service::DatabaseType::Redis
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseType<AWS, Managed> for MongoDB {
|
||||
type DatabaseOptions = DatabaseOptions;
|
||||
|
||||
fn short_name() -> &'static str {
|
||||
"DocumentDB"
|
||||
}
|
||||
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"mongodb"
|
||||
}
|
||||
|
||||
fn db_type() -> service::DatabaseType {
|
||||
service::DatabaseType::MongoDB
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: DatabaseType<AWS, Managed>> Database<AWS, Managed, T>
|
||||
where
|
||||
Database<AWS, Managed, T>: Service,
|
||||
{
|
||||
fn get_version_aws_managed(&self, event_details: EventDetails) -> Result<ServiceVersionCheckResult, EngineError> {
|
||||
let fn_version = match T::db_type() {
|
||||
service::DatabaseType::PostgreSQL => get_managed_postgres_version,
|
||||
service::DatabaseType::MongoDB => get_managed_mongodb_version,
|
||||
service::DatabaseType::MySQL => get_managed_mysql_version,
|
||||
service::DatabaseType::Redis => get_managed_redis_version,
|
||||
};
|
||||
|
||||
check_service_version(fn_version(self.version.to_string()), self, event_details, self.logger())
|
||||
}
|
||||
|
||||
fn to_tera_context_for_aws_managed(
|
||||
&self,
|
||||
target: &DeploymentTarget,
|
||||
options: &DatabaseOptions,
|
||||
) -> 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 = kubernetes.get_kubeconfig_file_path()?;
|
||||
context.insert("kubeconfig_path", &kube_config_file_path);
|
||||
|
||||
kubectl::kubectl_exec_create_namespace_without_labels(
|
||||
environment.namespace(),
|
||||
kube_config_file_path.as_str(),
|
||||
kubernetes.cloud_provider().credentials_environment_variables(),
|
||||
);
|
||||
|
||||
context.insert("namespace", environment.namespace());
|
||||
|
||||
let version = self
|
||||
.get_version_aws_managed(event_details)?
|
||||
.matched_version()
|
||||
.to_string();
|
||||
context.insert("version", &version);
|
||||
|
||||
// Specific to mysql
|
||||
if T::db_type() == service::DatabaseType::MySQL {
|
||||
context.insert(
|
||||
"parameter_group_family",
|
||||
&format!(
|
||||
"mysql{}.{}",
|
||||
self.version.major,
|
||||
self.version.minor.as_deref().unwrap_or_default()
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Specific for redis
|
||||
if T::db_type() == service::DatabaseType::Redis {
|
||||
let parameter_group_name = if self.version.major == "5" {
|
||||
"default.redis5.0"
|
||||
} else if self.version.major == "6" {
|
||||
"default.redis6.x"
|
||||
} else {
|
||||
"redis.unknown"
|
||||
};
|
||||
|
||||
context.insert("database_elasticache_parameter_group_name", parameter_group_name);
|
||||
}
|
||||
|
||||
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
|
||||
context.insert(k, v);
|
||||
}
|
||||
|
||||
context.insert("kubernetes_cluster_id", kubernetes.id());
|
||||
context.insert("kubernetes_cluster_name", kubernetes.name());
|
||||
context.insert("fqdn_id", self.fqdn_id.as_str());
|
||||
context.insert("fqdn", self.fqdn(target, &self.fqdn, Managed::is_managed()).as_str());
|
||||
context.insert("service_name", self.fqdn_id.as_str());
|
||||
context.insert("database_name", self.sanitized_name().as_str());
|
||||
context.insert("database_db_name", self.name());
|
||||
context.insert("database_login", options.login.as_str());
|
||||
context.insert("database_password", options.password.as_str());
|
||||
context.insert("database_port", &self.private_port());
|
||||
context.insert("database_disk_size_in_gib", &options.disk_size_in_gib);
|
||||
context.insert("database_instance_type", &self.database_instance_type);
|
||||
context.insert("database_disk_type", &options.database_disk_type);
|
||||
context.insert("encrypt_disk", &options.encrypt_disk);
|
||||
context.insert("database_ram_size_in_mib", &self.total_ram_in_mib);
|
||||
context.insert("database_total_cpus", &self.total_cpus);
|
||||
context.insert("database_fqdn", &options.host.as_str());
|
||||
context.insert("database_id", &self.id());
|
||||
context.insert("tfstate_suffix_name", &get_tfstate_suffix(self));
|
||||
context.insert("tfstate_name", &get_tfstate_name(self));
|
||||
context.insert("skip_final_snapshot", &false);
|
||||
context.insert("final_snapshot_name", &format!("qovery-{}-final-snap", self.id));
|
||||
context.insert("delete_automated_backups", &self.context().is_test_cluster());
|
||||
context.insert("publicly_accessible", &options.publicly_accessible);
|
||||
|
||||
if self.context.resource_expiration_in_seconds().is_some() {
|
||||
context.insert("resource_expiration_in_seconds", &self.context.resource_expiration_in_seconds())
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////:
|
||||
// POSTGRES SQL
|
||||
impl ToTeraContext for Database<AWS, Managed, PostgresSQL>
|
||||
where
|
||||
PostgresSQL: DatabaseType<AWS, Managed>,
|
||||
{
|
||||
fn to_tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
|
||||
self.to_tera_context_for_aws_managed(target, &self.options)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTeraContext for Database<AWS, Container, PostgresSQL>
|
||||
where
|
||||
PostgresSQL: DatabaseType<AWS, Container>,
|
||||
{
|
||||
fn to_tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
|
||||
self.to_tera_context_for_container(target, &self.options)
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////:
|
||||
// MySQL
|
||||
impl ToTeraContext for Database<AWS, Managed, MySQL>
|
||||
where
|
||||
MySQL: DatabaseType<AWS, Managed>,
|
||||
{
|
||||
fn to_tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
|
||||
self.to_tera_context_for_aws_managed(target, &self.options)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTeraContext for Database<AWS, Container, MySQL>
|
||||
where
|
||||
MySQL: DatabaseType<AWS, Container>,
|
||||
{
|
||||
fn to_tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
|
||||
self.to_tera_context_for_container(target, &self.options)
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////:
|
||||
// MongoDB
|
||||
impl ToTeraContext for Database<AWS, Managed, MongoDB>
|
||||
where
|
||||
MongoDB: DatabaseType<AWS, Managed>,
|
||||
{
|
||||
fn to_tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
|
||||
self.to_tera_context_for_aws_managed(target, &self.options)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTeraContext for Database<AWS, Container, MongoDB>
|
||||
where
|
||||
MongoDB: DatabaseType<AWS, Container>,
|
||||
{
|
||||
fn to_tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
|
||||
self.to_tera_context_for_container(target, &self.options)
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////:
|
||||
// Redis
|
||||
impl ToTeraContext for Database<AWS, Managed, Redis>
|
||||
where
|
||||
Redis: DatabaseType<AWS, Managed>,
|
||||
{
|
||||
fn to_tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
|
||||
self.to_tera_context_for_aws_managed(target, &self.options)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTeraContext for Database<AWS, Container, Redis>
|
||||
where
|
||||
Redis: DatabaseType<AWS, Container>,
|
||||
{
|
||||
fn to_tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
|
||||
self.to_tera_context_for_container(target, &self.options)
|
||||
}
|
||||
}
|
||||
198
src/models/aws/database_utils.rs
Normal file
198
src/models/aws/database_utils.rs
Normal file
@@ -0,0 +1,198 @@
|
||||
use crate::errors::CommandError;
|
||||
use crate::models::database_utils::{generate_supported_version, get_supported_version_to_use};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub(super) fn get_managed_mysql_version(requested_version: String) -> Result<String, CommandError> {
|
||||
let mut supported_mysql_versions = HashMap::new();
|
||||
// https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_MySQL.html#MySQL.Concepts.VersionMgmt
|
||||
|
||||
// v5.7
|
||||
let mut v57 = generate_supported_version(5, 7, 7, Some(16), Some(34), None);
|
||||
v57.remove("5.7.32");
|
||||
v57.remove("5.7.29");
|
||||
v57.remove("5.7.27");
|
||||
v57.remove("5.7.20");
|
||||
v57.remove("5.7.18");
|
||||
supported_mysql_versions.extend(v57);
|
||||
|
||||
// v8
|
||||
let mut v8 = generate_supported_version(8, 0, 0, Some(11), Some(26), None);
|
||||
v8.remove("8.0.24");
|
||||
v8.remove("8.0.22");
|
||||
v8.remove("8.0.18");
|
||||
v8.remove("8.0.14");
|
||||
v8.remove("8.0.12");
|
||||
supported_mysql_versions.extend(v8);
|
||||
|
||||
get_supported_version_to_use("RDS MySQL", supported_mysql_versions, requested_version)
|
||||
}
|
||||
|
||||
pub(super) fn get_managed_mongodb_version(requested_version: String) -> Result<String, CommandError> {
|
||||
let mut supported_mongodb_versions = HashMap::new();
|
||||
|
||||
// v3.6.0
|
||||
let mongo_version = generate_supported_version(3, 6, 6, Some(0), Some(0), None);
|
||||
supported_mongodb_versions.extend(mongo_version);
|
||||
|
||||
// v4.0.0
|
||||
let mongo_version = generate_supported_version(4, 0, 0, Some(0), Some(0), None);
|
||||
supported_mongodb_versions.extend(mongo_version);
|
||||
|
||||
get_supported_version_to_use("DocumentDB", supported_mongodb_versions, requested_version)
|
||||
}
|
||||
|
||||
pub(super) fn get_managed_postgres_version(requested_version: String) -> Result<String, CommandError> {
|
||||
let mut supported_postgres_versions = HashMap::new();
|
||||
|
||||
// https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_PostgreSQL.html#PostgreSQL.Concepts
|
||||
|
||||
// v10
|
||||
let mut v10 = generate_supported_version(10, 1, 18, None, None, None);
|
||||
v10.remove("10.2"); // non supported version by AWS
|
||||
v10.remove("10.8"); // non supported version by AWS
|
||||
supported_postgres_versions.extend(v10);
|
||||
|
||||
// v11
|
||||
let mut v11 = generate_supported_version(11, 1, 13, None, None, None);
|
||||
v11.remove("11.3"); // non supported version by AWS
|
||||
supported_postgres_versions.extend(v11);
|
||||
|
||||
// v12
|
||||
let v12 = generate_supported_version(12, 2, 8, None, None, None);
|
||||
supported_postgres_versions.extend(v12);
|
||||
|
||||
// v13
|
||||
let v13 = generate_supported_version(13, 1, 4, None, None, None);
|
||||
supported_postgres_versions.extend(v13);
|
||||
|
||||
get_supported_version_to_use("Postgresql", supported_postgres_versions, requested_version)
|
||||
}
|
||||
|
||||
pub(super) fn get_managed_redis_version(requested_version: String) -> Result<String, CommandError> {
|
||||
let mut supported_redis_versions = HashMap::with_capacity(2);
|
||||
// https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/supported-engine-versions.html
|
||||
|
||||
supported_redis_versions.insert("6".to_string(), "6.x".to_string());
|
||||
supported_redis_versions.insert("5".to_string(), "5.0.6".to_string());
|
||||
|
||||
get_supported_version_to_use("Elasticache", supported_redis_versions, requested_version)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::errors::ErrorMessageVerbosity::SafeOnly;
|
||||
use crate::models::aws::database_utils::{
|
||||
get_managed_mongodb_version, get_managed_mysql_version, get_managed_postgres_version, get_managed_redis_version,
|
||||
};
|
||||
use crate::models::database_utils::{
|
||||
get_self_hosted_mongodb_version, get_self_hosted_mysql_version, get_self_hosted_postgres_version,
|
||||
get_self_hosted_redis_version,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn check_postgres_version() {
|
||||
// managed version
|
||||
assert_eq!(get_managed_postgres_version("12".to_string()).unwrap(), "12.8");
|
||||
assert_eq!(get_managed_postgres_version("12.3".to_string()).unwrap(), "12.3");
|
||||
assert_eq!(
|
||||
get_managed_postgres_version("12.3.0".to_string())
|
||||
.unwrap_err()
|
||||
.message(SafeOnly)
|
||||
.as_str(),
|
||||
"Postgresql 12.3.0 version is not supported"
|
||||
);
|
||||
assert_eq!(
|
||||
get_managed_postgres_version("11.3".to_string())
|
||||
.unwrap_err()
|
||||
.message(SafeOnly)
|
||||
.as_str(),
|
||||
"Postgresql 11.3 version is not supported"
|
||||
);
|
||||
// self-hosted version
|
||||
assert_eq!(get_self_hosted_postgres_version("12".to_string()).unwrap(), "12.8.0");
|
||||
assert_eq!(get_self_hosted_postgres_version("12.8".to_string()).unwrap(), "12.8.0");
|
||||
assert_eq!(get_self_hosted_postgres_version("12.3.0".to_string()).unwrap(), "12.3.0");
|
||||
assert_eq!(
|
||||
get_self_hosted_postgres_version("1.0".to_string())
|
||||
.unwrap_err()
|
||||
.message(SafeOnly)
|
||||
.as_str(),
|
||||
"Postgresql 1.0 version is not supported"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_redis_version() {
|
||||
// managed version
|
||||
assert_eq!(get_managed_redis_version("6".to_string()).unwrap(), "6.x");
|
||||
assert_eq!(get_managed_redis_version("5".to_string()).unwrap(), "5.0.6");
|
||||
assert_eq!(
|
||||
get_managed_redis_version("1.0".to_string())
|
||||
.unwrap_err()
|
||||
.message(SafeOnly)
|
||||
.as_str(),
|
||||
"Elasticache 1.0 version is not supported"
|
||||
);
|
||||
|
||||
// self-hosted version
|
||||
assert_eq!(get_self_hosted_redis_version("6".to_string()).unwrap(), "6.0.9");
|
||||
assert_eq!(get_self_hosted_redis_version("6.0".to_string()).unwrap(), "6.0.9");
|
||||
assert_eq!(
|
||||
get_self_hosted_redis_version("1.0".to_string())
|
||||
.unwrap_err()
|
||||
.message(SafeOnly)
|
||||
.as_str(),
|
||||
"Redis 1.0 version is not supported"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_mysql_version() {
|
||||
// managed version
|
||||
assert_eq!(get_managed_mysql_version("8".to_string()).unwrap(), "8.0.26");
|
||||
assert_eq!(get_managed_mysql_version("8.0".to_string()).unwrap(), "8.0.26");
|
||||
assert_eq!(get_managed_mysql_version("8.0.16".to_string()).unwrap(), "8.0.16");
|
||||
assert_eq!(
|
||||
get_managed_mysql_version("8.0.18".to_string())
|
||||
.unwrap_err()
|
||||
.message(SafeOnly)
|
||||
.as_str(),
|
||||
"RDS MySQL 8.0.18 version is not supported"
|
||||
);
|
||||
// self-hosted version
|
||||
assert_eq!(get_self_hosted_mysql_version("5".to_string()).unwrap(), "5.7.34");
|
||||
assert_eq!(get_self_hosted_mysql_version("5.7".to_string()).unwrap(), "5.7.34");
|
||||
assert_eq!(get_self_hosted_mysql_version("5.7.31".to_string()).unwrap(), "5.7.31");
|
||||
assert_eq!(
|
||||
get_self_hosted_mysql_version("1.0".to_string())
|
||||
.unwrap_err()
|
||||
.message(SafeOnly)
|
||||
.as_str(),
|
||||
"MySQL 1.0 version is not supported"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_mongodb_version() {
|
||||
// managed version
|
||||
assert_eq!(get_managed_mongodb_version("4".to_string()).unwrap(), "4.0.0");
|
||||
assert_eq!(get_managed_mongodb_version("4.0".to_string()).unwrap(), "4.0.0");
|
||||
assert_eq!(
|
||||
get_managed_mongodb_version("4.4".to_string())
|
||||
.unwrap_err()
|
||||
.message(SafeOnly)
|
||||
.as_str(),
|
||||
"DocumentDB 4.4 version is not supported"
|
||||
);
|
||||
// self-hosted version
|
||||
assert_eq!(get_self_hosted_mongodb_version("4".to_string()).unwrap(), "4.4.4");
|
||||
assert_eq!(get_self_hosted_mongodb_version("4.2".to_string()).unwrap(), "4.2.12");
|
||||
assert_eq!(
|
||||
get_self_hosted_mongodb_version("3.4".to_string())
|
||||
.unwrap_err()
|
||||
.message(SafeOnly)
|
||||
.as_str(),
|
||||
"MongoDB 3.4 version is not supported"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod application;
|
||||
pub mod router;
|
||||
mod application;
|
||||
mod database;
|
||||
mod database_utils;
|
||||
mod router;
|
||||
|
||||
use crate::models::types::CloudProvider;
|
||||
use crate::models::types::AWS;
|
||||
@@ -30,7 +32,7 @@ impl CloudProvider for AWS {
|
||||
"Elastic Container Registry"
|
||||
}
|
||||
|
||||
fn helm_directory_name() -> &'static str {
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"aws"
|
||||
}
|
||||
}
|
||||
|
||||
508
src/models/database.rs
Normal file
508
src/models/database.rs
Normal file
@@ -0,0 +1,508 @@
|
||||
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, DatabaseOptions, Delete, Helm,
|
||||
Pause, Service, ServiceType, ServiceVersionCheckResult, StatefulService, Terraform,
|
||||
};
|
||||
use crate::cloud_provider::utilities::{check_domain_for, managed_db_name_sanitizer, print_action};
|
||||
use crate::cloud_provider::{service, DeploymentTarget};
|
||||
use crate::cmd::helm::Timeout;
|
||||
use crate::cmd::kubectl;
|
||||
use crate::errors::EngineError;
|
||||
use crate::events::{EnvironmentStep, EventDetails, Stage, ToTransmitter, Transmitter};
|
||||
use crate::io_models::{Context, Listen, Listener, Listeners, ListenersHelper};
|
||||
use crate::logger::Logger;
|
||||
use crate::models::database_utils::{
|
||||
get_self_hosted_mongodb_version, get_self_hosted_mysql_version, get_self_hosted_postgres_version,
|
||||
get_self_hosted_redis_version,
|
||||
};
|
||||
use crate::models::types::{CloudProvider, ToTeraContext, VersionsNumber};
|
||||
use function_name::named;
|
||||
use std::borrow::Borrow;
|
||||
use std::marker::PhantomData;
|
||||
use tera::Context as TeraContext;
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// Database mode
|
||||
pub struct Managed {}
|
||||
pub struct Container {}
|
||||
pub trait DatabaseMode {
|
||||
fn is_managed() -> bool;
|
||||
fn is_container() -> bool {
|
||||
!Self::is_managed()
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseMode for Managed {
|
||||
fn is_managed() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseMode for Container {
|
||||
fn is_managed() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// Database types, will be only used as a marker
|
||||
pub struct PostgresSQL {}
|
||||
pub struct MySQL {}
|
||||
pub struct MongoDB {}
|
||||
pub struct Redis {}
|
||||
|
||||
pub trait DatabaseType<T: CloudProvider, M: DatabaseMode> {
|
||||
type DatabaseOptions;
|
||||
|
||||
fn short_name() -> &'static str;
|
||||
fn lib_directory_name() -> &'static str;
|
||||
fn db_type() -> service::DatabaseType;
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum DatabaseError {
|
||||
#[error("Application invalid configuration: {0}")]
|
||||
InvalidConfig(String),
|
||||
}
|
||||
|
||||
pub struct Database<C: CloudProvider, M: DatabaseMode, T: DatabaseType<C, M>> {
|
||||
_marker: PhantomData<(C, M, T)>,
|
||||
pub(super) context: Context,
|
||||
pub(super) id: String,
|
||||
pub(super) action: Action,
|
||||
pub(super) name: String,
|
||||
pub(super) version: VersionsNumber,
|
||||
pub(super) fqdn: String,
|
||||
pub(super) fqdn_id: String,
|
||||
pub(super) total_cpus: String,
|
||||
pub(super) total_ram_in_mib: u32,
|
||||
pub(super) database_instance_type: String,
|
||||
pub(super) publicly_accessible: bool,
|
||||
pub(super) private_port: u16,
|
||||
pub(super) options: T::DatabaseOptions,
|
||||
pub(super) listeners: Listeners,
|
||||
pub(super) logger: Box<dyn Logger>,
|
||||
}
|
||||
|
||||
impl<C: CloudProvider, M: DatabaseMode, T: DatabaseType<C, M>> Database<C, M, T> {
|
||||
pub fn new(
|
||||
context: Context,
|
||||
id: &str,
|
||||
action: Action,
|
||||
name: &str,
|
||||
version: VersionsNumber,
|
||||
fqdn: &str,
|
||||
fqdn_id: &str,
|
||||
total_cpus: String,
|
||||
total_ram_in_mib: u32,
|
||||
database_instance_type: &str,
|
||||
publicly_accessible: bool,
|
||||
private_port: u16,
|
||||
options: T::DatabaseOptions,
|
||||
listeners: Listeners,
|
||||
logger: Box<dyn Logger>,
|
||||
) -> Result<Self, DatabaseError> {
|
||||
// TODO: Implement domain constraint logic
|
||||
|
||||
Ok(Self {
|
||||
_marker: PhantomData,
|
||||
context,
|
||||
action,
|
||||
id: id.to_string(),
|
||||
name: name.to_string(),
|
||||
version,
|
||||
fqdn: fqdn.to_string(),
|
||||
fqdn_id: fqdn_id.to_string(),
|
||||
total_cpus,
|
||||
total_ram_in_mib,
|
||||
database_instance_type: database_instance_type.to_string(),
|
||||
publicly_accessible,
|
||||
private_port,
|
||||
options,
|
||||
listeners,
|
||||
logger,
|
||||
})
|
||||
}
|
||||
|
||||
fn selector(&self) -> String {
|
||||
format!("databaseId={}", self.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CloudProvider, M: DatabaseMode, T: DatabaseType<C, M>> Terraform for Database<C, M, T> {
|
||||
fn terraform_common_resource_dir_path(&self) -> String {
|
||||
format!("{}/{}/services/common", self.context.lib_root_dir(), C::lib_directory_name())
|
||||
}
|
||||
|
||||
fn terraform_resource_dir_path(&self) -> String {
|
||||
format!(
|
||||
"{}/{}/services/{}",
|
||||
self.context.lib_root_dir(),
|
||||
C::lib_directory_name(),
|
||||
T::lib_directory_name()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CloudProvider, M: DatabaseMode, T: DatabaseType<C, M>> Listen for Database<C, M, T> {
|
||||
fn listeners(&self) -> &Listeners {
|
||||
&self.listeners
|
||||
}
|
||||
|
||||
fn add_listener(&mut self, listener: Listener) {
|
||||
self.listeners.push(listener);
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CloudProvider, M: DatabaseMode, T: DatabaseType<C, M>> ToTransmitter for Database<C, M, T> {
|
||||
fn to_transmitter(&self) -> Transmitter {
|
||||
Transmitter::Database(self.id.to_string(), T::short_name().to_string(), self.name.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CloudProvider, M: DatabaseMode, T: DatabaseType<C, M>> Service for Database<C, M, T>
|
||||
where
|
||||
Database<C, M, T>: ToTeraContext,
|
||||
{
|
||||
fn context(&self) -> &Context {
|
||||
&self.context
|
||||
}
|
||||
|
||||
fn service_type(&self) -> ServiceType {
|
||||
ServiceType::Database(T::db_type())
|
||||
}
|
||||
|
||||
fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn sanitized_name(&self) -> String {
|
||||
// FIXME: specific case only for aws ;'(
|
||||
// This is sad, but can't change that as it would break/wipe all container db for users
|
||||
if C::lib_directory_name() == "aws" {
|
||||
managed_db_name_sanitizer(60, T::lib_directory_name(), &self.id)
|
||||
} else {
|
||||
format!("{}-{}", T::lib_directory_name(), &self.id)
|
||||
}
|
||||
}
|
||||
|
||||
fn version(&self) -> String {
|
||||
self.version.to_string()
|
||||
}
|
||||
|
||||
fn action(&self) -> &Action {
|
||||
&self.action
|
||||
}
|
||||
|
||||
fn private_port(&self) -> Option<u16> {
|
||||
Some(self.private_port)
|
||||
}
|
||||
|
||||
fn start_timeout(&self) -> Timeout<u32> {
|
||||
Timeout::Default
|
||||
}
|
||||
|
||||
fn total_cpus(&self) -> String {
|
||||
self.total_cpus.to_string()
|
||||
}
|
||||
|
||||
fn cpu_burst(&self) -> String {
|
||||
self.total_cpus.to_string()
|
||||
}
|
||||
|
||||
fn total_ram_in_mib(&self) -> u32 {
|
||||
self.total_ram_in_mib
|
||||
}
|
||||
|
||||
fn min_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn max_instances(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn publicly_accessible(&self) -> bool {
|
||||
self.publicly_accessible
|
||||
}
|
||||
|
||||
fn tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
|
||||
self.to_tera_context(target)
|
||||
}
|
||||
|
||||
fn logger(&self) -> &dyn Logger {
|
||||
self.logger.borrow()
|
||||
}
|
||||
|
||||
fn selector(&self) -> Option<String> {
|
||||
Some(self.selector())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Cloud: CloudProvider, M: DatabaseMode, DbType: DatabaseType<Cloud, M>> Helm for Database<Cloud, M, DbType> {
|
||||
fn helm_selector(&self) -> Option<String> {
|
||||
Some(self.selector())
|
||||
}
|
||||
|
||||
fn helm_release_name(&self) -> String {
|
||||
format!("{}-{}", DbType::lib_directory_name(), self.id)
|
||||
}
|
||||
|
||||
fn helm_chart_dir(&self) -> String {
|
||||
format!(
|
||||
"{}/common/services/{}",
|
||||
self.context.lib_root_dir(),
|
||||
DbType::lib_directory_name()
|
||||
)
|
||||
}
|
||||
|
||||
fn helm_chart_values_dir(&self) -> String {
|
||||
format!(
|
||||
"{}/{}/chart_values/{}",
|
||||
self.context.lib_root_dir(),
|
||||
Cloud::lib_directory_name(),
|
||||
DbType::lib_directory_name()
|
||||
)
|
||||
}
|
||||
|
||||
fn helm_chart_external_name_service_dir(&self) -> String {
|
||||
format!("{}/common/charts/external-name-svc", self.context.lib_root_dir())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CloudProvider, M: DatabaseMode, T: DatabaseType<C, M>> Create for Database<C, M, T>
|
||||
where
|
||||
Database<C, M, T>: ToTeraContext,
|
||||
{
|
||||
#[named]
|
||||
fn on_create(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
|
||||
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
|
||||
print_action(
|
||||
C::short_name(),
|
||||
T::db_type().to_string().as_str(),
|
||||
function_name!(),
|
||||
self.name(),
|
||||
event_details.clone(),
|
||||
self.logger(),
|
||||
);
|
||||
|
||||
send_progress_on_long_task(self, Action::Create, || {
|
||||
deploy_stateful_service(target, self, event_details.clone(), self.logger())
|
||||
})
|
||||
}
|
||||
|
||||
#[named]
|
||||
fn on_create_check(&self) -> Result<(), EngineError> {
|
||||
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
|
||||
print_action(
|
||||
C::short_name(),
|
||||
T::db_type().to_string().as_str(),
|
||||
function_name!(),
|
||||
self.name(),
|
||||
event_details.clone(),
|
||||
self.logger(),
|
||||
);
|
||||
|
||||
if self.publicly_accessible {
|
||||
check_domain_for(
|
||||
ListenersHelper::new(&self.listeners),
|
||||
vec![&self.fqdn],
|
||||
self.context.execution_id(),
|
||||
self.context.execution_id(),
|
||||
event_details,
|
||||
self.logger(),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[named]
|
||||
fn on_create_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
|
||||
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Deploy));
|
||||
print_action(
|
||||
C::short_name(),
|
||||
T::db_type().to_string().as_str(),
|
||||
function_name!(),
|
||||
self.name(),
|
||||
event_details,
|
||||
self.logger(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CloudProvider, M: DatabaseMode, T: DatabaseType<C, M>> Pause for Database<C, M, T>
|
||||
where
|
||||
Database<C, M, T>: ToTeraContext,
|
||||
{
|
||||
#[named]
|
||||
fn on_pause(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
|
||||
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
|
||||
print_action(
|
||||
C::short_name(),
|
||||
T::db_type().to_string().as_str(),
|
||||
function_name!(),
|
||||
self.name(),
|
||||
event_details,
|
||||
self.logger(),
|
||||
);
|
||||
|
||||
send_progress_on_long_task(self, Action::Pause, || scale_down_database(target, self, 0))
|
||||
}
|
||||
|
||||
fn on_pause_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[named]
|
||||
fn on_pause_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
|
||||
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Pause));
|
||||
print_action(
|
||||
C::short_name(),
|
||||
T::db_type().to_string().as_str(),
|
||||
function_name!(),
|
||||
self.name(),
|
||||
event_details,
|
||||
self.logger(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CloudProvider, M: DatabaseMode, T: DatabaseType<C, M>> Delete for Database<C, M, T>
|
||||
where
|
||||
Database<C, M, T>: ToTeraContext,
|
||||
{
|
||||
#[named]
|
||||
fn on_delete(&self, target: &DeploymentTarget) -> Result<(), EngineError> {
|
||||
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
|
||||
print_action(
|
||||
C::short_name(),
|
||||
T::db_type().to_string().as_str(),
|
||||
function_name!(),
|
||||
self.name(),
|
||||
event_details.clone(),
|
||||
self.logger(),
|
||||
);
|
||||
|
||||
send_progress_on_long_task(self, Action::Delete, || {
|
||||
delete_stateful_service(target, self, event_details.clone(), self.logger())
|
||||
})
|
||||
}
|
||||
|
||||
fn on_delete_check(&self) -> Result<(), EngineError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[named]
|
||||
fn on_delete_error(&self, _target: &DeploymentTarget) -> Result<(), EngineError> {
|
||||
let event_details = self.get_event_details(Stage::Environment(EnvironmentStep::Delete));
|
||||
print_action(
|
||||
C::short_name(),
|
||||
T::db_type().to_string().as_str(),
|
||||
function_name!(),
|
||||
self.name(),
|
||||
event_details,
|
||||
self.logger(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CloudProvider, M: DatabaseMode, T: DatabaseType<C, M>> StatefulService for Database<C, M, T>
|
||||
where
|
||||
Database<C, M, T>: ToTeraContext,
|
||||
{
|
||||
fn as_stateful_service(&self) -> &dyn StatefulService {
|
||||
self
|
||||
}
|
||||
|
||||
fn is_managed_service(&self) -> bool {
|
||||
M::is_managed()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CloudProvider, M: DatabaseMode, T: DatabaseType<C, M>> service::Database for Database<C, M, T> where
|
||||
Database<C, M, T>: ToTeraContext
|
||||
{
|
||||
}
|
||||
|
||||
impl<C: CloudProvider, M: DatabaseMode, T: DatabaseType<C, M>> Database<C, M, T>
|
||||
where
|
||||
Database<C, M, T>: Service,
|
||||
{
|
||||
fn get_version(&self, event_details: EventDetails) -> Result<ServiceVersionCheckResult, EngineError> {
|
||||
let fn_version = match T::db_type() {
|
||||
service::DatabaseType::PostgreSQL => get_self_hosted_postgres_version,
|
||||
service::DatabaseType::MongoDB => get_self_hosted_mongodb_version,
|
||||
service::DatabaseType::MySQL => get_self_hosted_mysql_version,
|
||||
service::DatabaseType::Redis => get_self_hosted_redis_version,
|
||||
};
|
||||
|
||||
check_service_version(fn_version(self.version.to_string()), self, event_details, self.logger())
|
||||
}
|
||||
|
||||
pub(super) fn to_tera_context_for_container(
|
||||
&self,
|
||||
target: &DeploymentTarget,
|
||||
options: &DatabaseOptions,
|
||||
) -> 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 = kubernetes.get_kubeconfig_file_path()?;
|
||||
context.insert("kubeconfig_path", &kube_config_file_path);
|
||||
|
||||
kubectl::kubectl_exec_create_namespace_without_labels(
|
||||
environment.namespace(),
|
||||
kube_config_file_path.as_str(),
|
||||
kubernetes.cloud_provider().credentials_environment_variables(),
|
||||
);
|
||||
|
||||
context.insert("namespace", environment.namespace());
|
||||
|
||||
let version = self.get_version(event_details)?.matched_version().to_string();
|
||||
context.insert("version", &version);
|
||||
|
||||
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
|
||||
context.insert(k, v);
|
||||
}
|
||||
|
||||
context.insert("kubernetes_cluster_id", kubernetes.id());
|
||||
context.insert("kubernetes_cluster_name", kubernetes.name());
|
||||
|
||||
context.insert("fqdn_id", self.fqdn_id.as_str());
|
||||
context.insert("fqdn", self.fqdn(target, &self.fqdn, M::is_managed()).as_str());
|
||||
context.insert("service_name", self.fqdn_id.as_str());
|
||||
context.insert("database_db_name", self.name());
|
||||
context.insert("database_login", options.login.as_str());
|
||||
context.insert("database_password", options.password.as_str());
|
||||
context.insert("database_port", &self.private_port());
|
||||
context.insert("database_disk_size_in_gib", &options.disk_size_in_gib);
|
||||
context.insert("database_instance_type", &self.database_instance_type);
|
||||
context.insert("database_disk_type", &options.database_disk_type);
|
||||
context.insert("database_ram_size_in_mib", &self.total_ram_in_mib);
|
||||
context.insert("database_total_cpus", &self.total_cpus);
|
||||
context.insert("database_fqdn", &options.host.as_str());
|
||||
context.insert("database_id", &self.id());
|
||||
context.insert("tfstate_suffix_name", &get_tfstate_suffix(self));
|
||||
context.insert("tfstate_name", &get_tfstate_name(self));
|
||||
context.insert("publicly_accessible", &self.publicly_accessible);
|
||||
|
||||
if self.context.resource_expiration_in_seconds().is_some() {
|
||||
context.insert("resource_expiration_in_seconds", &self.context.resource_expiration_in_seconds())
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
}
|
||||
199
src/models/database_utils.rs
Normal file
199
src/models/database_utils.rs
Normal file
@@ -0,0 +1,199 @@
|
||||
use crate::errors::CommandError;
|
||||
use crate::models::types::VersionsNumber;
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub fn get_self_hosted_postgres_version(requested_version: String) -> Result<String, CommandError> {
|
||||
let mut supported_postgres_versions = HashMap::new();
|
||||
|
||||
// https://hub.docker.com/r/bitnami/postgresql/tags?page=1&ordering=last_updated
|
||||
|
||||
// v10
|
||||
let v10 = generate_supported_version(10, 1, 16, Some(0), Some(0), None);
|
||||
supported_postgres_versions.extend(v10);
|
||||
|
||||
// v11
|
||||
let v11 = generate_supported_version(11, 1, 11, Some(0), Some(0), None);
|
||||
supported_postgres_versions.extend(v11);
|
||||
|
||||
// v12
|
||||
let v12 = generate_supported_version(12, 2, 8, Some(0), Some(0), None);
|
||||
supported_postgres_versions.extend(v12);
|
||||
|
||||
// v13
|
||||
let v13 = generate_supported_version(13, 1, 4, Some(0), Some(0), None);
|
||||
supported_postgres_versions.extend(v13);
|
||||
|
||||
get_supported_version_to_use("Postgresql", supported_postgres_versions, requested_version)
|
||||
}
|
||||
|
||||
pub fn get_self_hosted_mysql_version(requested_version: String) -> Result<String, CommandError> {
|
||||
let mut supported_mysql_versions = HashMap::new();
|
||||
// https://hub.docker.com/r/bitnami/mysql/tags?page=1&ordering=last_updated
|
||||
|
||||
// v5.7
|
||||
let v57 = generate_supported_version(5, 7, 7, Some(16), Some(34), None);
|
||||
supported_mysql_versions.extend(v57);
|
||||
|
||||
// v8
|
||||
let v8 = generate_supported_version(8, 0, 0, Some(11), Some(24), None);
|
||||
supported_mysql_versions.extend(v8);
|
||||
|
||||
get_supported_version_to_use("MySQL", supported_mysql_versions, requested_version)
|
||||
}
|
||||
|
||||
pub fn get_self_hosted_mongodb_version(requested_version: String) -> Result<String, CommandError> {
|
||||
let mut supported_mongodb_versions = HashMap::new();
|
||||
|
||||
// https://hub.docker.com/r/bitnami/mongodb/tags?page=1&ordering=last_updated
|
||||
|
||||
// v3.6
|
||||
let mongo_version = generate_supported_version(3, 6, 6, Some(0), Some(22), None);
|
||||
supported_mongodb_versions.extend(mongo_version);
|
||||
|
||||
// v4.0
|
||||
let mongo_version = generate_supported_version(4, 0, 0, Some(0), Some(23), None);
|
||||
supported_mongodb_versions.extend(mongo_version);
|
||||
|
||||
// v4.2
|
||||
let mongo_version = generate_supported_version(4, 2, 2, Some(0), Some(12), None);
|
||||
supported_mongodb_versions.extend(mongo_version);
|
||||
|
||||
// v4.4
|
||||
let mongo_version = generate_supported_version(4, 4, 4, Some(0), Some(4), None);
|
||||
supported_mongodb_versions.extend(mongo_version);
|
||||
|
||||
get_supported_version_to_use("MongoDB", supported_mongodb_versions, requested_version)
|
||||
}
|
||||
|
||||
pub fn get_self_hosted_redis_version(requested_version: String) -> Result<String, CommandError> {
|
||||
let mut supported_redis_versions = HashMap::with_capacity(4);
|
||||
// https://hub.docker.com/r/bitnami/redis/tags?page=1&ordering=last_updated
|
||||
|
||||
supported_redis_versions.insert("6".to_string(), "6.0.9".to_string());
|
||||
supported_redis_versions.insert("6.0".to_string(), "6.0.9".to_string());
|
||||
supported_redis_versions.insert("5".to_string(), "5.0.10".to_string());
|
||||
supported_redis_versions.insert("5.0".to_string(), "5.0.10".to_string());
|
||||
|
||||
get_supported_version_to_use("Redis", supported_redis_versions, requested_version)
|
||||
}
|
||||
|
||||
pub fn get_supported_version_to_use(
|
||||
database_name: &str,
|
||||
all_supported_versions: HashMap<String, String>,
|
||||
version_to_check: String,
|
||||
) -> Result<String, CommandError> {
|
||||
let version = VersionsNumber::from_str(version_to_check.as_str())?;
|
||||
|
||||
// if a patch version is required
|
||||
if version.patch.is_some() {
|
||||
return match all_supported_versions.get(&format!(
|
||||
"{}.{}.{}",
|
||||
version.major,
|
||||
version.minor.unwrap(),
|
||||
version.patch.unwrap()
|
||||
)) {
|
||||
Some(version) => Ok(version.to_string()),
|
||||
None => {
|
||||
return Err(CommandError::new_from_safe_message(format!(
|
||||
"{} {} version is not supported",
|
||||
database_name, version_to_check
|
||||
)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// if a minor version is required
|
||||
if version.minor.is_some() {
|
||||
return match all_supported_versions.get(&format!("{}.{}", version.major, version.minor.unwrap())) {
|
||||
Some(version) => Ok(version.to_string()),
|
||||
None => {
|
||||
return Err(CommandError::new_from_safe_message(format!(
|
||||
"{} {} version is not supported",
|
||||
database_name, version_to_check
|
||||
)));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// if only a major version is required
|
||||
match all_supported_versions.get(&version.major) {
|
||||
Some(version) => Ok(version.to_string()),
|
||||
None => {
|
||||
return Err(CommandError::new_from_safe_message(format!(
|
||||
"{} {} version is not supported",
|
||||
database_name, version_to_check
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ease the support of multiple versions by range
|
||||
pub fn generate_supported_version(
|
||||
major: i32,
|
||||
minor_min: i32,
|
||||
minor_max: i32,
|
||||
update_min: Option<i32>,
|
||||
update_max: Option<i32>,
|
||||
suffix_version: Option<String>,
|
||||
) -> HashMap<String, String> {
|
||||
let mut supported_versions = HashMap::new();
|
||||
let latest_major_version;
|
||||
|
||||
// blank suffix if not requested
|
||||
let suffix = match suffix_version {
|
||||
Some(suffix) => suffix,
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
let _ = match update_min {
|
||||
// manage minor with updates
|
||||
Some(_) => {
|
||||
latest_major_version = format!("{}.{}.{}{}", major, minor_max, update_max.unwrap(), suffix);
|
||||
|
||||
if minor_min == minor_max {
|
||||
// add short minor format targeting latest version
|
||||
supported_versions.insert(format!("{}.{}", major, minor_max), latest_major_version.clone());
|
||||
if update_min.unwrap() == update_max.unwrap() {
|
||||
let version = format!("{}.{}.{}", major, minor_min, update_min.unwrap());
|
||||
supported_versions.insert(version.clone(), format!("{}{}", version, suffix));
|
||||
} else {
|
||||
for update in update_min.unwrap()..update_max.unwrap() + 1 {
|
||||
let version = format!("{}.{}.{}", major, minor_min, update);
|
||||
supported_versions.insert(version.clone(), format!("{}{}", version, suffix));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for minor in minor_min..minor_max + 1 {
|
||||
// add short minor format targeting latest version
|
||||
supported_versions.insert(
|
||||
format!("{}.{}", major, minor),
|
||||
format!("{}.{}.{}", major, minor, update_max.unwrap()),
|
||||
);
|
||||
if update_min.unwrap() == update_max.unwrap() {
|
||||
let version = format!("{}.{}.{}", major, minor, update_min.unwrap());
|
||||
supported_versions.insert(version.clone(), format!("{}{}", version, suffix));
|
||||
} else {
|
||||
for update in update_min.unwrap()..update_max.unwrap() + 1 {
|
||||
let version = format!("{}.{}.{}", major, minor, update);
|
||||
supported_versions.insert(version.clone(), format!("{}{}", version, suffix));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// manage minor without updates
|
||||
None => {
|
||||
latest_major_version = format!("{}.{}{}", major, minor_max, suffix);
|
||||
for minor in minor_min..minor_max + 1 {
|
||||
let version = format!("{}.{}", major, minor);
|
||||
supported_versions.insert(version.clone(), format!("{}{}", version, suffix));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// default major + major.minor supported version
|
||||
supported_versions.insert(major.to_string(), latest_major_version);
|
||||
|
||||
supported_versions
|
||||
}
|
||||
152
src/models/digital_ocean/database.rs
Normal file
152
src/models/digital_ocean/database.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
use crate::cloud_provider::service::{check_service_version, DatabaseOptions, Service};
|
||||
use crate::cloud_provider::{service, DeploymentTarget};
|
||||
use crate::errors::EngineError;
|
||||
use crate::models::database::{Container, Database, DatabaseType, MongoDB, MySQL, PostgresSQL, Redis};
|
||||
use crate::models::database_utils::{
|
||||
get_self_hosted_mongodb_version, get_self_hosted_mysql_version, get_self_hosted_postgres_version,
|
||||
get_self_hosted_redis_version,
|
||||
};
|
||||
use crate::models::types::{ToTeraContext, DO};
|
||||
use tera::Context as TeraContext;
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// CONTAINER
|
||||
impl DatabaseType<DO, Container> for PostgresSQL {
|
||||
type DatabaseOptions = DatabaseOptions;
|
||||
|
||||
fn short_name() -> &'static str {
|
||||
"PostgresSQL"
|
||||
}
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"postgresql"
|
||||
}
|
||||
fn db_type() -> service::DatabaseType {
|
||||
service::DatabaseType::PostgreSQL
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseType<DO, Container> for MySQL {
|
||||
type DatabaseOptions = DatabaseOptions;
|
||||
|
||||
fn short_name() -> &'static str {
|
||||
"MySQL"
|
||||
}
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"mysql"
|
||||
}
|
||||
fn db_type() -> service::DatabaseType {
|
||||
service::DatabaseType::MySQL
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseType<DO, Container> for Redis {
|
||||
type DatabaseOptions = DatabaseOptions;
|
||||
|
||||
fn short_name() -> &'static str {
|
||||
"Redis"
|
||||
}
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"redis"
|
||||
}
|
||||
|
||||
fn db_type() -> service::DatabaseType {
|
||||
service::DatabaseType::Redis
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseType<DO, Container> for MongoDB {
|
||||
type DatabaseOptions = DatabaseOptions;
|
||||
|
||||
fn short_name() -> &'static str {
|
||||
"Redis"
|
||||
}
|
||||
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"mongodb"
|
||||
}
|
||||
|
||||
fn db_type() -> service::DatabaseType {
|
||||
service::DatabaseType::MongoDB
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// MANAGED
|
||||
// DO don't support managed databases for now
|
||||
|
||||
////////////////////////////////////////////////////////////////////////:
|
||||
// POSTGRES SQL
|
||||
impl ToTeraContext for Database<DO, Container, PostgresSQL>
|
||||
where
|
||||
PostgresSQL: DatabaseType<DO, Container>,
|
||||
{
|
||||
fn to_tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
|
||||
let _check_version = |event_details| {
|
||||
check_service_version(
|
||||
get_self_hosted_postgres_version(self.version.to_string()),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
};
|
||||
self.to_tera_context_for_container(target, &self.options)
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////:
|
||||
// MySQL
|
||||
impl ToTeraContext for Database<DO, Container, MySQL>
|
||||
where
|
||||
MySQL: DatabaseType<DO, Container>,
|
||||
{
|
||||
fn to_tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
|
||||
let _check_version = |event_details| {
|
||||
check_service_version(
|
||||
get_self_hosted_mysql_version(self.version.to_string()),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
};
|
||||
self.to_tera_context_for_container(target, &self.options)
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////:
|
||||
// MongoDB
|
||||
impl ToTeraContext for Database<DO, Container, MongoDB>
|
||||
where
|
||||
MongoDB: DatabaseType<DO, Container>,
|
||||
{
|
||||
fn to_tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
|
||||
let _check_version = |event_details| {
|
||||
check_service_version(
|
||||
get_self_hosted_mongodb_version(self.version.to_string()),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
};
|
||||
|
||||
self.to_tera_context_for_container(target, &self.options)
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////:
|
||||
// Redis
|
||||
impl ToTeraContext for Database<DO, Container, Redis>
|
||||
where
|
||||
Redis: DatabaseType<DO, Container>,
|
||||
{
|
||||
fn to_tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
|
||||
let _check_version = |event_details| {
|
||||
check_service_version(
|
||||
get_self_hosted_redis_version(self.version.to_string()),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
};
|
||||
self.to_tera_context_for_container(target, &self.options)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
mod application;
|
||||
mod database;
|
||||
mod router;
|
||||
|
||||
use crate::errors::CommandError;
|
||||
@@ -34,7 +35,7 @@ impl CloudProvider for DO {
|
||||
"Digital Ocean Container Registry"
|
||||
}
|
||||
|
||||
fn helm_directory_name() -> &'static str {
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"digitalocean"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod application;
|
||||
pub mod aws;
|
||||
pub mod database;
|
||||
pub(crate) mod database_utils;
|
||||
pub mod digital_ocean;
|
||||
pub mod router;
|
||||
pub mod scaleway;
|
||||
|
||||
@@ -218,7 +218,7 @@ impl<T: CloudProvider> Helm for Router<T> {
|
||||
format!(
|
||||
"{}/{}/chart_values/nginx-ingress",
|
||||
self.context.lib_root_dir(),
|
||||
T::helm_directory_name()
|
||||
T::lib_directory_name()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@ where
|
||||
let from_dir = format!(
|
||||
"{}/{}/charts/q-ingress-tls",
|
||||
self.context.lib_root_dir(),
|
||||
T::helm_directory_name()
|
||||
T::lib_directory_name()
|
||||
);
|
||||
if let Err(e) =
|
||||
crate::template::generate_and_copy_all_files_into_dir(from_dir.as_str(), workspace_dir.as_str(), context)
|
||||
|
||||
293
src/models/scaleway/database.rs
Normal file
293
src/models/scaleway/database.rs
Normal file
@@ -0,0 +1,293 @@
|
||||
use crate::cloud_provider::service::{
|
||||
check_service_version, default_tera_context, get_tfstate_name, get_tfstate_suffix, DatabaseOptions, Service,
|
||||
ServiceVersionCheckResult,
|
||||
};
|
||||
use crate::cloud_provider::{service, DeploymentTarget};
|
||||
use crate::cmd::kubectl;
|
||||
use crate::errors::EngineError;
|
||||
use crate::events::{EnvironmentStep, EventDetails, Stage};
|
||||
use crate::models::database::{
|
||||
Container, Database, DatabaseMode, DatabaseType, Managed, MongoDB, MySQL, PostgresSQL, Redis,
|
||||
};
|
||||
use crate::models::database_utils::{
|
||||
get_self_hosted_mongodb_version, get_self_hosted_mysql_version, get_self_hosted_postgres_version,
|
||||
get_self_hosted_redis_version,
|
||||
};
|
||||
use crate::models::scaleway::database_utils::{pick_managed_mysql_version, pick_managed_postgres_version};
|
||||
use crate::models::types::{ToTeraContext, SCW};
|
||||
use tera::Context as TeraContext;
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// CONTAINER
|
||||
impl DatabaseType<SCW, Container> for PostgresSQL {
|
||||
type DatabaseOptions = DatabaseOptions;
|
||||
|
||||
fn short_name() -> &'static str {
|
||||
"PostgresSQL"
|
||||
}
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"postgresql"
|
||||
}
|
||||
fn db_type() -> service::DatabaseType {
|
||||
service::DatabaseType::PostgreSQL
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseType<SCW, Container> for MySQL {
|
||||
type DatabaseOptions = DatabaseOptions;
|
||||
|
||||
fn short_name() -> &'static str {
|
||||
"MySQL"
|
||||
}
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"mysql"
|
||||
}
|
||||
fn db_type() -> service::DatabaseType {
|
||||
service::DatabaseType::MySQL
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseType<SCW, Container> for Redis {
|
||||
type DatabaseOptions = DatabaseOptions;
|
||||
|
||||
fn short_name() -> &'static str {
|
||||
"Redis"
|
||||
}
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"redis"
|
||||
}
|
||||
|
||||
fn db_type() -> service::DatabaseType {
|
||||
service::DatabaseType::Redis
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseType<SCW, Container> for MongoDB {
|
||||
type DatabaseOptions = DatabaseOptions;
|
||||
|
||||
fn short_name() -> &'static str {
|
||||
"Redis"
|
||||
}
|
||||
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"mongodb"
|
||||
}
|
||||
|
||||
fn db_type() -> service::DatabaseType {
|
||||
service::DatabaseType::MongoDB
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// MANAGED
|
||||
impl DatabaseType<SCW, Managed> for PostgresSQL {
|
||||
type DatabaseOptions = DatabaseOptions;
|
||||
|
||||
fn short_name() -> &'static str {
|
||||
"Postgres Managed"
|
||||
}
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"postgresql"
|
||||
}
|
||||
|
||||
fn db_type() -> service::DatabaseType {
|
||||
service::DatabaseType::PostgreSQL
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseType<SCW, Managed> for MySQL {
|
||||
type DatabaseOptions = DatabaseOptions;
|
||||
|
||||
fn short_name() -> &'static str {
|
||||
"MySQL Managed"
|
||||
}
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"mysql"
|
||||
}
|
||||
|
||||
fn db_type() -> service::DatabaseType {
|
||||
service::DatabaseType::MySQL
|
||||
}
|
||||
}
|
||||
|
||||
// Redis and MongoDB are not supported managed db yet
|
||||
|
||||
impl<M: DatabaseMode, T: DatabaseType<SCW, M>> Database<SCW, M, T> {
|
||||
fn to_tera_context_for_scaleway_managed(
|
||||
&self,
|
||||
target: &DeploymentTarget,
|
||||
options: &DatabaseOptions,
|
||||
get_version: &dyn Fn(EventDetails) -> Result<ServiceVersionCheckResult, EngineError>,
|
||||
) -> Result<TeraContext, EngineError>
|
||||
where
|
||||
Database<SCW, M, T>: Service,
|
||||
{
|
||||
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 = kubernetes.get_kubeconfig_file_path()?;
|
||||
context.insert("kubeconfig_path", &kube_config_file_path);
|
||||
|
||||
kubectl::kubectl_exec_create_namespace_without_labels(
|
||||
environment.namespace(),
|
||||
kube_config_file_path.as_str(),
|
||||
kubernetes.cloud_provider().credentials_environment_variables(),
|
||||
);
|
||||
|
||||
context.insert("namespace", environment.namespace());
|
||||
|
||||
let version = get_version(event_details)?.matched_version();
|
||||
context.insert("version_major", &version.to_major_version_string());
|
||||
context.insert("version", &version.to_string()); // Scaleway needs to have major version only
|
||||
|
||||
for (k, v) in kubernetes.cloud_provider().tera_context_environment_variables() {
|
||||
context.insert(k, v);
|
||||
}
|
||||
|
||||
context.insert("kubernetes_cluster_id", kubernetes.id());
|
||||
context.insert("kubernetes_cluster_name", kubernetes.name());
|
||||
|
||||
context.insert("fqdn_id", self.fqdn_id.as_str());
|
||||
context.insert("fqdn", self.fqdn(target, &self.fqdn, M::is_managed()).as_str());
|
||||
context.insert("service_name", self.fqdn_id.as_str());
|
||||
context.insert("database_name", self.sanitized_name().as_str());
|
||||
context.insert("database_db_name", self.name());
|
||||
context.insert("database_login", options.login.as_str());
|
||||
context.insert("database_password", options.password.as_str());
|
||||
context.insert("database_port", &self.private_port());
|
||||
context.insert("database_disk_size_in_gib", &options.disk_size_in_gib);
|
||||
context.insert("database_instance_type", &self.database_instance_type);
|
||||
context.insert("database_disk_type", &options.database_disk_type);
|
||||
context.insert("database_ram_size_in_mib", &self.total_ram_in_mib);
|
||||
context.insert("database_total_cpus", &self.total_cpus);
|
||||
context.insert("database_fqdn", &options.host.as_str());
|
||||
context.insert("database_id", &self.id());
|
||||
context.insert("tfstate_suffix_name", &get_tfstate_suffix(self));
|
||||
context.insert("tfstate_name", &get_tfstate_name(self));
|
||||
|
||||
context.insert("publicly_accessible", &options.publicly_accessible);
|
||||
context.insert("activate_high_availability", &options.activate_high_availability);
|
||||
context.insert("activate_backups", &options.activate_backups);
|
||||
context.insert("delete_automated_backups", &self.context().is_test_cluster());
|
||||
if self.context.resource_expiration_in_seconds().is_some() {
|
||||
context.insert("resource_expiration_in_seconds", &self.context.resource_expiration_in_seconds())
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////:
|
||||
// POSTGRES SQL
|
||||
impl ToTeraContext for Database<SCW, Managed, PostgresSQL>
|
||||
where
|
||||
PostgresSQL: DatabaseType<SCW, Managed>,
|
||||
{
|
||||
fn to_tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
|
||||
let check_version = |event_details| {
|
||||
check_service_version(
|
||||
pick_managed_postgres_version(self.version.to_string()),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
};
|
||||
self.to_tera_context_for_scaleway_managed(target, &self.options, &check_version)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTeraContext for Database<SCW, Container, PostgresSQL>
|
||||
where
|
||||
PostgresSQL: DatabaseType<SCW, Container>,
|
||||
{
|
||||
fn to_tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
|
||||
let _check_version = |event_details| {
|
||||
check_service_version(
|
||||
get_self_hosted_postgres_version(self.version.to_string()),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
};
|
||||
self.to_tera_context_for_container(target, &self.options)
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////:
|
||||
// MySQL
|
||||
impl ToTeraContext for Database<SCW, Managed, MySQL>
|
||||
where
|
||||
MySQL: DatabaseType<SCW, Managed>,
|
||||
{
|
||||
fn to_tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
|
||||
let check_version = |event_details| {
|
||||
check_service_version(
|
||||
pick_managed_mysql_version(self.version.to_string()),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
};
|
||||
self.to_tera_context_for_scaleway_managed(target, &self.options, &check_version)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTeraContext for Database<SCW, Container, MySQL>
|
||||
where
|
||||
MySQL: DatabaseType<SCW, Container>,
|
||||
{
|
||||
fn to_tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
|
||||
let _check_version = |event_details| {
|
||||
check_service_version(
|
||||
get_self_hosted_mysql_version(self.version.to_string()),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
};
|
||||
self.to_tera_context_for_container(target, &self.options)
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////:
|
||||
// MongoDB
|
||||
impl ToTeraContext for Database<SCW, Container, MongoDB>
|
||||
where
|
||||
MongoDB: DatabaseType<SCW, Container>,
|
||||
{
|
||||
fn to_tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
|
||||
let _check_version = |event_details| {
|
||||
check_service_version(
|
||||
get_self_hosted_mongodb_version(self.version.to_string()),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
};
|
||||
|
||||
self.to_tera_context_for_container(target, &self.options)
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////:
|
||||
// Redis
|
||||
impl ToTeraContext for Database<SCW, Container, Redis>
|
||||
where
|
||||
Redis: DatabaseType<SCW, Container>,
|
||||
{
|
||||
fn to_tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError> {
|
||||
let _check_version = |event_details| {
|
||||
check_service_version(
|
||||
get_self_hosted_redis_version(self.version.to_string()),
|
||||
self,
|
||||
event_details,
|
||||
self.logger(),
|
||||
)
|
||||
};
|
||||
self.to_tera_context_for_container(target, &self.options)
|
||||
}
|
||||
}
|
||||
36
src/models/scaleway/database_utils.rs
Normal file
36
src/models/scaleway/database_utils.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use crate::errors::CommandError;
|
||||
use crate::models::database_utils::get_supported_version_to_use;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub(super) 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();
|
||||
|
||||
// {"name":"PostgreSQL","version":"13","end_of_life":"2025-11-13T00:00:00Z"}
|
||||
// {"name":"PostgreSQL","version":"12","end_of_life":"2024-11-14T00:00:00Z"}
|
||||
// {"name":"PostgreSQL","version":"11","end_of_life":"2023-11-09T00:00:00Z"}
|
||||
// {"name":"PostgreSQL","version":"10","end_of_life":"2022-11-10T00:00:00Z"}
|
||||
supported_postgres_versions.insert("10".to_string(), "10".to_string());
|
||||
supported_postgres_versions.insert("10.0".to_string(), "10.0".to_string());
|
||||
supported_postgres_versions.insert("11".to_string(), "11".to_string());
|
||||
supported_postgres_versions.insert("11.0".to_string(), "11.0".to_string());
|
||||
supported_postgres_versions.insert("12".to_string(), "12".to_string());
|
||||
supported_postgres_versions.insert("12.0".to_string(), "12.0".to_string());
|
||||
supported_postgres_versions.insert("13".to_string(), "13".to_string());
|
||||
supported_postgres_versions.insert("13.0".to_string(), "13.0".to_string());
|
||||
|
||||
get_supported_version_to_use("RDB postgres", supported_postgres_versions, requested_version)
|
||||
}
|
||||
|
||||
pub(super) 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();
|
||||
|
||||
// {"name": "MySQL", "version":"8","end_of_life":"2026-04-01T00:00:00Z"}
|
||||
supported_mysql_versions.insert("8".to_string(), "8".to_string());
|
||||
supported_mysql_versions.insert("8.0".to_string(), "8.0".to_string());
|
||||
|
||||
get_supported_version_to_use("RDB MySQL", supported_mysql_versions, requested_version)
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
mod application;
|
||||
mod database;
|
||||
mod database_utils;
|
||||
mod router;
|
||||
|
||||
use crate::errors::CommandError;
|
||||
@@ -33,7 +35,7 @@ impl CloudProvider for SCW {
|
||||
"Scaleway Container Registry"
|
||||
}
|
||||
|
||||
fn helm_directory_name() -> &'static str {
|
||||
fn lib_directory_name() -> &'static str {
|
||||
"scaleway"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::fmt::Write;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::cloud_provider::DeploymentTarget;
|
||||
use crate::errors::EngineError;
|
||||
use crate::errors::{CommandError, EngineError};
|
||||
use tera::Context as TeraContext;
|
||||
|
||||
// Those types are just marker types that are use to tag our struct/object model
|
||||
@@ -19,9 +24,106 @@ pub trait CloudProvider {
|
||||
fn full_name() -> &'static str;
|
||||
fn registry_short_name() -> &'static str;
|
||||
fn registry_full_name() -> &'static str;
|
||||
fn helm_directory_name() -> &'static str;
|
||||
fn lib_directory_name() -> &'static str;
|
||||
}
|
||||
|
||||
pub(crate) trait ToTeraContext {
|
||||
fn to_tera_context(&self, target: &DeploymentTarget) -> Result<TeraContext, EngineError>;
|
||||
}
|
||||
|
||||
// unfortunately some proposed versions are not SemVer like Elasticache (6.x)
|
||||
// this is why we need ot have our own structure
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub struct VersionsNumber {
|
||||
pub(crate) major: String,
|
||||
pub(crate) minor: Option<String>,
|
||||
pub(crate) patch: Option<String>,
|
||||
pub(crate) suffix: Option<String>,
|
||||
}
|
||||
|
||||
impl VersionsNumber {
|
||||
pub fn new(major: String, minor: Option<String>, patch: Option<String>, suffix: Option<String>) -> Self {
|
||||
VersionsNumber {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
suffix,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_major_version_string(&self) -> String {
|
||||
self.major.clone()
|
||||
}
|
||||
|
||||
pub fn to_major_minor_version_string(&self, default_minor: &str) -> String {
|
||||
let test = format!(
|
||||
"{}.{}",
|
||||
self.major.clone(),
|
||||
self.minor.as_ref().unwrap_or(&default_minor.to_string())
|
||||
);
|
||||
|
||||
test
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for VersionsNumber {
|
||||
type Err = CommandError;
|
||||
|
||||
fn from_str(version: &str) -> Result<Self, Self::Err> {
|
||||
if version.trim() == "" {
|
||||
return Err(CommandError::new_from_safe_message("version cannot be empty".to_string()));
|
||||
}
|
||||
|
||||
let mut version_split = version.splitn(4, '.').map(|v| v.trim());
|
||||
|
||||
let major = match version_split.next() {
|
||||
Some(major) => {
|
||||
let major = major.to_string();
|
||||
major.replace('v', "")
|
||||
}
|
||||
None => {
|
||||
return Err(CommandError::new_from_safe_message(format!(
|
||||
"please check the version you've sent ({}), it can't be checked",
|
||||
version
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
let minor = version_split.next().map(|minor| {
|
||||
let minor = minor.to_string();
|
||||
minor.replace('+', "")
|
||||
});
|
||||
|
||||
let patch = version_split.next().map(|patch| patch.to_string());
|
||||
|
||||
let suffix = version_split.next().map(|suffix| suffix.to_string());
|
||||
|
||||
// TODO(benjaminch): Handle properly the case where versions are empty
|
||||
// eq. 1..2
|
||||
|
||||
Ok(VersionsNumber::new(major, minor, patch, suffix))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for VersionsNumber {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(&self.major)?;
|
||||
|
||||
if let Some(minor) = &self.minor {
|
||||
f.write_char('.')?;
|
||||
f.write_str(minor)?;
|
||||
}
|
||||
|
||||
if let Some(patch) = &self.patch {
|
||||
f.write_char('.')?;
|
||||
f.write_str(patch)?;
|
||||
}
|
||||
|
||||
if let Some(suffix) = &self.suffix {
|
||||
f.write_char('.')?;
|
||||
f.write_str(suffix)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,7 +323,7 @@ fn postgresql_deploy_a_working_environment_and_redeploy() {
|
||||
assert!(matches!(ret, TransactionResult::Ok));
|
||||
|
||||
// TO CHECK: DATABASE SHOULDN'T BE RESTARTED AFTER A REDEPLOY
|
||||
let database_name = format!("postgresql{}-0", &environment_check.databases[0].name);
|
||||
let database_name = format!("postgresql{}-0", &environment_check.databases[0].id);
|
||||
match is_pod_restarted_env(context, Kind::Aws, environment_check, database_name.as_str(), secrets) {
|
||||
(true, _) => assert!(true),
|
||||
(false, _) => assert!(false),
|
||||
|
||||
@@ -360,7 +360,7 @@ fn postgresql_deploy_a_working_environment_and_redeploy() {
|
||||
assert!(matches!(result, TransactionResult::Ok));
|
||||
|
||||
// TO CHECK: DATABASE SHOULDN'T BE RESTARTED AFTER A REDEPLOY
|
||||
let database_name = format!("postgresql-{}-0", &environment_check.databases[0].name);
|
||||
let database_name = format!("postgresql-{}-0", &environment_check.databases[0].id);
|
||||
match is_pod_restarted_env(
|
||||
context.clone(),
|
||||
ProviderKind::Scw,
|
||||
|
||||
Reference in New Issue
Block a user