Compare commits
2 Commits
54080eb0c9
...
0ad768a3a3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ad768a3a3 | ||
|
|
6b3172d88b |
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
target/
|
||||
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug
|
||||
target
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# Generated by cargo mutants
|
||||
# Contains mutation testing data
|
||||
**/mutants.out*/
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
2129
Cargo.lock
generated
Normal file
2129
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"apps/container",
|
||||
]
|
||||
|
||||
resolver = "3"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
module_inception = "allow"
|
||||
1
apps/container/.gitignore
vendored
Normal file
1
apps/container/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
generated/
|
||||
17
apps/container/Cargo.toml
Normal file
17
apps/container/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "container-simulate"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
name = "container"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.89"
|
||||
testcontainers = "0.24.0"
|
||||
shared = { path = "../../public/shared" }
|
||||
tokio = { version = "1.47.0", features = ["full"] }
|
||||
url = "2.5.7"
|
||||
clap = { version = "4.5.48", features = ["derive", "env"] }
|
||||
path-clean = "1.0.1"
|
||||
59
apps/container/src/db.rs
Normal file
59
apps/container/src/db.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
pub mod config;
|
||||
pub mod postgresql;
|
||||
pub mod sqlite;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use shared::db_type::DBType;
|
||||
use std::future::Future;
|
||||
use std::{pin::Pin, sync::Arc};
|
||||
use url::Host;
|
||||
|
||||
use testcontainers::{ContainerAsync, GenericImage, TestcontainersError};
|
||||
|
||||
use crate::{ConfigInfoType, WithContainer, WithoutContainer};
|
||||
|
||||
pub type UnStartedContainer =
|
||||
Pin<Box<dyn Future<Output = Result<ContainerAsync<GenericImage>, TestcontainersError>> + Send>>;
|
||||
|
||||
pub type DBConfigInfoType = ConfigInfoType<ContainerizedDBInfo, PreExistingDBInfo>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PreExistingDBInfo {
|
||||
pub db_type: DBType,
|
||||
pub url: String,
|
||||
pub on_delete: Arc<dyn Fn() + Send + Sync>,
|
||||
}
|
||||
|
||||
impl WithoutContainer for PreExistingDBInfo {
|
||||
fn on_delete(&self) {
|
||||
(self.on_delete)();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ContainerizedDBInfo {
|
||||
pub db_type: DBType,
|
||||
pub container: Arc<ContainerAsync<GenericImage>>,
|
||||
pub container_name: String,
|
||||
pub database_name: String,
|
||||
pub host: Host,
|
||||
pub port: u16,
|
||||
pub url: String,
|
||||
pub user: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
impl WithContainer for ContainerizedDBInfo {
|
||||
fn container(&self) -> &Arc<ContainerAsync<GenericImage>> {
|
||||
&self.container
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait DBInfo<T> {
|
||||
async fn new(config: Option<T>) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
async fn get_db_container_config_info(&self) -> DBConfigInfoType;
|
||||
fn get_unstarted_container(&self) -> Result<UnStartedContainer, ()>;
|
||||
}
|
||||
53
apps/container/src/db/config.rs
Normal file
53
apps/container/src/db/config.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
pub struct OptionalContainerConfig {
|
||||
pub image: Option<String>,
|
||||
pub tag: Option<String>,
|
||||
pub container_name: Option<String>,
|
||||
pub database_name: Option<String>,
|
||||
pub user: Option<String>,
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ContainerConfig {
|
||||
pub image: String,
|
||||
pub tag: String,
|
||||
pub container_name: String,
|
||||
pub database_name: String,
|
||||
pub user: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
impl OptionalContainerConfig {
|
||||
pub fn fill_with(&self, other: &ContainerConfig) -> ContainerConfig {
|
||||
ContainerConfig {
|
||||
image: self.image.clone().unwrap_or_else(|| other.image.clone()),
|
||||
tag: self.tag.clone().unwrap_or_else(|| other.tag.clone()),
|
||||
container_name: self
|
||||
.container_name
|
||||
.clone()
|
||||
.unwrap_or_else(|| other.container_name.clone()),
|
||||
database_name: self
|
||||
.database_name
|
||||
.clone()
|
||||
.unwrap_or_else(|| other.database_name.clone()),
|
||||
user: self.user.clone().unwrap_or_else(|| other.user.clone()),
|
||||
password: self
|
||||
.password
|
||||
.clone()
|
||||
.unwrap_or_else(|| other.password.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OptionalContainerConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
image: None,
|
||||
tag: None,
|
||||
container_name: None,
|
||||
database_name: None,
|
||||
user: None,
|
||||
password: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
95
apps/container/src/db/postgresql.rs
Normal file
95
apps/container/src/db/postgresql.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use testcontainers::{
|
||||
GenericImage, ImageExt,
|
||||
core::{IntoContainerPort, WaitFor},
|
||||
runners::AsyncRunner,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ConfigInfoType,
|
||||
db::{
|
||||
ContainerizedDBInfo, DBConfigInfoType, DBInfo, UnStartedContainer,
|
||||
config::{ContainerConfig, OptionalContainerConfig},
|
||||
},
|
||||
};
|
||||
|
||||
pub fn get_default_config() -> ContainerConfig {
|
||||
ContainerConfig {
|
||||
container_name: "yanpm-postgres".to_string(),
|
||||
database_name: "postgres".to_string(),
|
||||
user: "postgres".to_string(),
|
||||
password: "postgres".to_string(),
|
||||
image: "postgres".to_string(),
|
||||
tag: "16-alpine".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PostgreSQLContainer {
|
||||
pub config: ContainerConfig,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl DBInfo<OptionalContainerConfig> for PostgreSQLContainer {
|
||||
async fn get_db_container_config_info(&self) -> DBConfigInfoType {
|
||||
let pg_container = self
|
||||
.get_unstarted_container()
|
||||
.unwrap()
|
||||
.await
|
||||
.expect("Failed to start PostgreSQL container");
|
||||
let pg_host = pg_container.get_host().await.expect("Failed to get host");
|
||||
let pg_host_port = pg_container
|
||||
.get_host_port_ipv4(5432.tcp())
|
||||
.await
|
||||
.expect("Failed to get host port");
|
||||
let pg_url = format!(
|
||||
"postgres://{}:{}@{}:{}/{}",
|
||||
self.config.user,
|
||||
self.config.password,
|
||||
pg_host,
|
||||
pg_host_port,
|
||||
self.config.database_name
|
||||
);
|
||||
|
||||
ConfigInfoType::Containerized(ContainerizedDBInfo {
|
||||
db_type: crate::db::DBType::PostgreSQL,
|
||||
container: Arc::new(pg_container),
|
||||
container_name: self.config.container_name.clone(),
|
||||
database_name: self.config.database_name.clone(),
|
||||
host: pg_host,
|
||||
port: pg_host_port,
|
||||
url: pg_url,
|
||||
user: self.config.user.clone(),
|
||||
password: self.config.password.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn new(user_default_config: Option<OptionalContainerConfig>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let default_config = get_default_config();
|
||||
let config = user_default_config
|
||||
.unwrap_or_default()
|
||||
.fill_with(&default_config);
|
||||
|
||||
PostgreSQLContainer {
|
||||
config: config.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_unstarted_container(&self) -> Result<UnStartedContainer, ()> {
|
||||
Ok(
|
||||
GenericImage::new(self.config.image.clone(), self.config.tag.clone())
|
||||
.with_exposed_port(5432.tcp())
|
||||
.with_wait_for(WaitFor::message_on_stderr(
|
||||
"database system is ready to accept connections",
|
||||
))
|
||||
.with_container_name(self.config.container_name.clone())
|
||||
.with_env_var("POSTGRES_USER", self.config.user.clone())
|
||||
.with_env_var("POSTGRES_PASSWORD", self.config.password.clone())
|
||||
.start(),
|
||||
)
|
||||
}
|
||||
}
|
||||
113
apps/container/src/db/sqlite.rs
Normal file
113
apps/container/src/db/sqlite.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::{
|
||||
ConfigInfoType,
|
||||
db::{DBConfigInfoType, DBInfo, PreExistingDBInfo, UnStartedContainer},
|
||||
util::to_absolute_path,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ContainerConfig {
|
||||
// Add any SQLite-specific configuration options here if needed
|
||||
pub database_name: String,
|
||||
pub absolute_dir_path: PathBuf,
|
||||
}
|
||||
|
||||
pub struct OptionalContainerConfig {
|
||||
// Add any optional configuration fields here
|
||||
pub database_name: Option<String>,
|
||||
pub absolute_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl OptionalContainerConfig {
|
||||
pub fn fill_with(&self, other: &ContainerConfig) -> ContainerConfig {
|
||||
ContainerConfig {
|
||||
database_name: self
|
||||
.database_name
|
||||
.clone()
|
||||
.unwrap_or_else(|| other.database_name.clone()),
|
||||
absolute_dir_path: self
|
||||
.absolute_path
|
||||
.clone()
|
||||
.unwrap_or_else(|| other.absolute_dir_path.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OptionalContainerConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
database_name: None,
|
||||
absolute_path: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_default_config() -> ContainerConfig {
|
||||
ContainerConfig {
|
||||
database_name: "sqlite".to_string(),
|
||||
absolute_dir_path: to_absolute_path("./generated/sqlite"),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SQLiteContainer {
|
||||
pub config: ContainerConfig,
|
||||
}
|
||||
|
||||
impl SQLiteContainer {
|
||||
fn get_db_absolute_path(&self) -> PathBuf {
|
||||
self.config
|
||||
.absolute_dir_path
|
||||
.join(&self.config.database_name)
|
||||
.with_extension("db")
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl DBInfo<OptionalContainerConfig> for SQLiteContainer {
|
||||
async fn get_db_container_config_info(&self) -> DBConfigInfoType {
|
||||
// sqlite filepath url does not include the "sqlite://" prefix
|
||||
let sqlite_url = format!("{}", self.get_db_absolute_path().to_string_lossy());
|
||||
// create the file
|
||||
std::fs::create_dir_all(&self.config.absolute_dir_path)
|
||||
.expect("Failed to create directories for SQLite database");
|
||||
std::fs::File::create(self.get_db_absolute_path())
|
||||
.expect("Failed to create SQLite database file");
|
||||
//
|
||||
ConfigInfoType::PreExisting(PreExistingDBInfo {
|
||||
db_type: crate::db::DBType::SQLite,
|
||||
url: sqlite_url,
|
||||
on_delete: {
|
||||
let db_path = self.get_db_absolute_path();
|
||||
Arc::new(move || {
|
||||
// delete the sqlite database file
|
||||
if db_path.exists() {
|
||||
if let Err(e) = std::fs::remove_file(&db_path) {
|
||||
eprintln!("Failed to delete SQLite database file: {}", e);
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async fn new(user_default_config: Option<OptionalContainerConfig>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let default_config = get_default_config();
|
||||
let config = user_default_config
|
||||
.unwrap_or_default()
|
||||
.fill_with(&default_config);
|
||||
|
||||
SQLiteContainer {
|
||||
config: config.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_unstarted_container(&self) -> Result<UnStartedContainer, ()> {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
36
apps/container/src/env.rs
Normal file
36
apps/container/src/env.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use std::io::Write;
|
||||
|
||||
use shared::db_type::DBType;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum EnvFileType {
|
||||
DotEnv,
|
||||
Yaml,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EnvFile {
|
||||
pub file_type: EnvFileType,
|
||||
pub db_type: DBType,
|
||||
pub db_url: String,
|
||||
}
|
||||
|
||||
impl EnvFile {
|
||||
pub fn write(self, path: impl AsRef<std::path::Path>) {
|
||||
let path_ref = path.as_ref();
|
||||
println!("Config file path: {}", path_ref.display());
|
||||
let mut config_file =
|
||||
std::fs::File::create(path_ref).expect("Failed to create config file");
|
||||
//
|
||||
self._write_line(&mut config_file, "DB_TYPE", &self.db_type.to_string());
|
||||
self._write_line(&mut config_file, "DATABASE_URL", &self.db_url.to_string())
|
||||
}
|
||||
|
||||
fn _write_line(&self, file: &mut std::fs::File, key: &str, value: &str) {
|
||||
match self.file_type {
|
||||
EnvFileType::DotEnv => writeln!(file, "{}={}", key, value),
|
||||
EnvFileType::Yaml => writeln!(file, "{}: \"{}\"", key, value),
|
||||
}
|
||||
.expect("Failed to write to config file");
|
||||
}
|
||||
}
|
||||
95
apps/container/src/lib.rs
Normal file
95
apps/container/src/lib.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
pub mod db;
|
||||
mod env;
|
||||
pub mod types;
|
||||
mod util;
|
||||
|
||||
use crate::{
|
||||
db::DBConfigInfoType,
|
||||
types::{ConfigInfoType, WithContainer, WithoutContainer},
|
||||
util::{
|
||||
await_termination_signal, remove_file_if_exists, stop_container, to_absolute_path,
|
||||
write_env_files,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Config {
|
||||
pub database: DBConfigInfoType,
|
||||
}
|
||||
|
||||
// relative to the pwd
|
||||
const API_CONFIG_PATH: &str = "../api/generated-config.yaml";
|
||||
const DB_CONFIG_PATH: &str = "../../public/database/.env.generated";
|
||||
|
||||
pub struct DetachedHandle<'a> {
|
||||
stopped: bool,
|
||||
pub config: &'a Config,
|
||||
}
|
||||
|
||||
impl<'a> DetachedHandle<'a> {
|
||||
pub fn new(config: &'a Config) -> Self {
|
||||
DetachedHandle {
|
||||
stopped: false,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn stop(&mut self) {
|
||||
if self.stopped {
|
||||
eprintln!("Attempted to stop an already stopped DetachedHandle.");
|
||||
return;
|
||||
}
|
||||
self.stopped = true;
|
||||
stop(self.config).await;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for DetachedHandle<'a> {
|
||||
fn drop(&mut self) {
|
||||
if self.stopped {
|
||||
return;
|
||||
}
|
||||
eprintln!(
|
||||
"Warning: DetachedHandle was dropped without calling stop(). The container may still be running."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async fn start(config: &Config) {
|
||||
let db_config = &config.database;
|
||||
//
|
||||
// write the config files for the api server and database client
|
||||
println!("Writing config files...");
|
||||
write_env_files(&db_config);
|
||||
println!("Config files written to:");
|
||||
println!(" - {}", to_absolute_path(API_CONFIG_PATH).display());
|
||||
println!(" - {}", to_absolute_path(DB_CONFIG_PATH).display());
|
||||
}
|
||||
|
||||
async fn stop(config: &Config) {
|
||||
let db_config = &config.database;
|
||||
// stop the container
|
||||
println!("Stopping container...");
|
||||
stop_container(db_config, "database".to_string()).await;
|
||||
// remove the generated config file
|
||||
println!("Removing generated config file...");
|
||||
remove_file_if_exists(DB_CONFIG_PATH);
|
||||
remove_file_if_exists(API_CONFIG_PATH);
|
||||
println!("Container stopped.");
|
||||
}
|
||||
|
||||
pub async fn start_attached(config: &Config) {
|
||||
start(config).await;
|
||||
|
||||
// wait for user input, ctrl+c, or other signals before exiting
|
||||
println!("Press Ctrl+C, or send SIGTERM to stop the container...");
|
||||
await_termination_signal().await;
|
||||
|
||||
stop(config).await;
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub async fn start_detached(config: &'_ Config) -> DetachedHandle<'_> {
|
||||
start(config).await;
|
||||
DetachedHandle::new(config)
|
||||
}
|
||||
53
apps/container/src/main.rs
Normal file
53
apps/container/src/main.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use clap::Parser;
|
||||
use container::Config;
|
||||
use container::start_attached;
|
||||
|
||||
use container::db::DBInfo;
|
||||
|
||||
/// Command line arguments
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Database type to use: 'postgres' or 'sqlite'. Can also be set with DB_TYPE env var.
|
||||
#[arg(long, default_value = "sqlite", env = "DB_TYPE")]
|
||||
db_type: String,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Parse command line arguments and environment variables
|
||||
let args = Args::parse();
|
||||
|
||||
println!("Starting container with database type: {}", args.db_type);
|
||||
let db_config = match args.db_type.to_lowercase().as_str() {
|
||||
"postgres" | "pg" | "pgsql" => {
|
||||
use container::db::postgresql::PostgreSQLContainer;
|
||||
println!("Using PostgreSQL database");
|
||||
PostgreSQLContainer::new(None)
|
||||
.await
|
||||
.get_db_container_config_info()
|
||||
.await
|
||||
}
|
||||
"sqlite" | "sql" => {
|
||||
println!("Using SQLite database");
|
||||
use container::db::sqlite::SQLiteContainer;
|
||||
SQLiteContainer::new(None)
|
||||
.await
|
||||
.get_db_container_config_info()
|
||||
.await
|
||||
}
|
||||
other => {
|
||||
eprintln!("Unknown db_type: {}. Use 'postgres' or 'sqlite'", other);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
println!("Database configuration obtained.");
|
||||
|
||||
let config = Config {
|
||||
database: db_config,
|
||||
};
|
||||
|
||||
println!("Starting container...");
|
||||
start_attached(&config).await;
|
||||
println!("Container stopped. Exiting...");
|
||||
}
|
||||
21
apps/container/src/types.rs
Normal file
21
apps/container/src/types.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use testcontainers::{ContainerAsync, GenericImage};
|
||||
|
||||
pub trait WithContainer {
|
||||
fn container(&self) -> &Arc<ContainerAsync<GenericImage>>;
|
||||
}
|
||||
|
||||
pub trait WithoutContainer {
|
||||
fn on_delete(&self);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ConfigInfoType<T, U>
|
||||
where
|
||||
T: WithContainer,
|
||||
U: WithoutContainer,
|
||||
{
|
||||
Containerized(T),
|
||||
PreExisting(U),
|
||||
}
|
||||
114
apps/container/src/util.rs
Normal file
114
apps/container/src/util.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use path_clean::PathClean;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tokio::signal::unix::{SignalKind, signal};
|
||||
|
||||
use crate::{
|
||||
API_CONFIG_PATH, DB_CONFIG_PATH,
|
||||
db::DBConfigInfoType,
|
||||
env::{self, EnvFile},
|
||||
types::{ConfigInfoType, WithContainer, WithoutContainer},
|
||||
};
|
||||
|
||||
// relative to the current working directory
|
||||
pub fn to_absolute_path(path: &str) -> PathBuf {
|
||||
if Path::new(path).is_absolute() {
|
||||
return PathBuf::from(path);
|
||||
}
|
||||
std::env::current_dir()
|
||||
.map(|cwd| cwd.join(path))
|
||||
.unwrap_or_else(|_| PathBuf::from(path))
|
||||
.clean()
|
||||
}
|
||||
|
||||
pub fn write_env_files(db_config: &DBConfigInfoType) {
|
||||
let api_config_path_absolute = to_absolute_path(API_CONFIG_PATH);
|
||||
let db_config_path_absolute = to_absolute_path(DB_CONFIG_PATH);
|
||||
|
||||
let (db_type, db_url) = match db_config {
|
||||
DBConfigInfoType::Containerized(config) => (config.db_type.clone(), config.url.clone()),
|
||||
DBConfigInfoType::PreExisting(config) => (config.db_type.clone(), config.url.clone()),
|
||||
};
|
||||
|
||||
let api_env_file = EnvFile {
|
||||
file_type: env::EnvFileType::Yaml,
|
||||
db_type: db_type,
|
||||
db_url: db_url,
|
||||
};
|
||||
|
||||
let mut db_env_file = api_env_file.clone();
|
||||
db_env_file.file_type = env::EnvFileType::DotEnv;
|
||||
|
||||
api_env_file.write(&api_config_path_absolute);
|
||||
db_env_file.write(&db_config_path_absolute);
|
||||
}
|
||||
|
||||
pub async fn stop_container(
|
||||
config: &ConfigInfoType<impl WithContainer, impl WithoutContainer>,
|
||||
config_name: String,
|
||||
) {
|
||||
match config {
|
||||
ConfigInfoType::Containerized(container_info) => {
|
||||
let container = container_info.container();
|
||||
if let Err(e) = container.stop().await {
|
||||
eprintln!(
|
||||
"Failed to stop container: {}. With error: {}",
|
||||
config_name, e
|
||||
);
|
||||
} else {
|
||||
println!("Container {} stopped successfully.", config_name);
|
||||
}
|
||||
}
|
||||
ConfigInfoType::PreExisting(pre_existing_info) => {
|
||||
pre_existing_info.on_delete();
|
||||
println!(
|
||||
"Pre-existing resource for {} cleaned up successfully.",
|
||||
config_name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_file_if_exists(path: &str) {
|
||||
let path = std::path::Path::new(path);
|
||||
if path.exists() {
|
||||
if let Err(e) = std::fs::remove_file(path) {
|
||||
eprintln!("Failed to remove file {}: {}", path.display(), e);
|
||||
} else {
|
||||
println!("Removed existing file: {}", path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn await_termination_signal() {
|
||||
tokio::select! {
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
println!("\nReceived Ctrl+C, stopping container...");
|
||||
}
|
||||
_ = async {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let mut sigterm = signal(SignalKind::terminate()).expect("Failed to register SIGTERM handler");
|
||||
sigterm.recv().await;
|
||||
println!("\nReceived SIGTERM, stopping container...");
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
// On non-Unix systems, just wait indefinitely
|
||||
std::future::pending::<()>().await;
|
||||
}
|
||||
} => {}
|
||||
_ = async {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let mut sigquit = signal(SignalKind::quit()).expect("Failed to register SIGQUIT handler");
|
||||
sigquit.recv().await;
|
||||
println!("\nReceived SIGQUIT, stopping container...");
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
// On non-Unix systems, just wait indefinitely
|
||||
std::future::pending::<()>().await;
|
||||
}
|
||||
} => {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user