Refactor container definitions
This commit is contained in:
115
apps/container/src/containers/agent.rs
Normal file
115
apps/container/src/containers/agent.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
use std::{error::Error, sync::Arc};
|
||||
use testcontainers::{
|
||||
ContainerAsync, GenericBuildableImage, GenericImage, ImageExt,
|
||||
core::{AccessMode, BuildImageOptions, ContainerPort, Mount, WaitFor},
|
||||
runners::{AsyncBuilder, AsyncRunner},
|
||||
};
|
||||
|
||||
use crate::{WithContainer, containers::UnStartedContainer};
|
||||
|
||||
pub const SOCK_NAME: &str = "yanpm-agent.sock";
|
||||
const SOCK_FOLDER: &str = "/var/run/yanpm";
|
||||
const NGINX_CONFIG_DIR: &str = "/etc/nginx/conf.d";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AgentContainerConfig {
|
||||
pub image: String,
|
||||
pub tag: String,
|
||||
pub container_name: String,
|
||||
pub dockerfile_path: String,
|
||||
pub force_build: bool,
|
||||
pub agent_config: AgentConfig,
|
||||
pub nginx_config: NginxConfig,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AgentContainerInfo {
|
||||
pub container: Arc<ContainerAsync<GenericImage>>,
|
||||
pub config: AgentContainerConfig,
|
||||
}
|
||||
|
||||
impl WithContainer for AgentContainerInfo {
|
||||
fn container(&self) -> &Arc<ContainerAsync<GenericImage>> {
|
||||
&self.container
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AgentConfig {
|
||||
pub sock_folder: String, // path to be mounted to host for unix socket
|
||||
pub nginx_config_dir: String, // path to be mounted to host for nginx config files, only the agent generated folder will be mounted
|
||||
pub sock_perm: u32, // permissions to set on the unix socket
|
||||
pub sock_gid: String, // GID to set on the unix socket
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NginxConfig {
|
||||
pub expose_http: bool,
|
||||
pub expose_https: bool,
|
||||
}
|
||||
|
||||
impl AgentContainerConfig {
|
||||
pub fn new(
|
||||
image: String,
|
||||
tag: String,
|
||||
container_name: String,
|
||||
dockerfile_path: String,
|
||||
force_build: bool,
|
||||
// agent configs
|
||||
agent_config: AgentConfig,
|
||||
nginx_config: NginxConfig,
|
||||
) -> Self {
|
||||
AgentContainerConfig {
|
||||
image,
|
||||
tag,
|
||||
container_name,
|
||||
dockerfile_path,
|
||||
force_build,
|
||||
// default agent configs
|
||||
agent_config,
|
||||
nginx_config,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_unstarted_container(&self) -> Result<UnStartedContainer, Box<dyn Error>> {
|
||||
let mut image = GenericBuildableImage::new(&self.image, &self.tag)
|
||||
.with_dockerfile(&self.dockerfile_path)
|
||||
.build_image_with(BuildImageOptions::new().with_skip_if_exists(!self.force_build))
|
||||
.await?;
|
||||
|
||||
if self.nginx_config.expose_http {
|
||||
image = image.with_exposed_port(ContainerPort::Tcp(80));
|
||||
}
|
||||
|
||||
if self.nginx_config.expose_https {
|
||||
image = image.with_exposed_port(ContainerPort::Tcp(443));
|
||||
}
|
||||
|
||||
image = image.with_wait_for(WaitFor::message_on_either_std("Starting yanpm-daemon on"));
|
||||
|
||||
Ok(image
|
||||
.with_container_name(self.container_name.clone())
|
||||
.with_env_var("YANPM_AGENT_SOCK", format!("{}/{}", SOCK_FOLDER, SOCK_NAME))
|
||||
.with_env_var("YANPM_NGINX_CONFIG_DIR", NGINX_CONFIG_DIR.to_string())
|
||||
.with_env_var(
|
||||
"YANPM_AGENT_SOCK_PERM",
|
||||
self.agent_config.sock_perm.to_string(),
|
||||
)
|
||||
.with_env_var("YANPM_AGENT_SOCK_GID", self.agent_config.sock_gid.clone())
|
||||
.with_mount(
|
||||
Mount::bind_mount(
|
||||
self.agent_config.sock_folder.clone(),
|
||||
SOCK_FOLDER.to_string(),
|
||||
)
|
||||
.with_access_mode(AccessMode::ReadWrite),
|
||||
)
|
||||
.with_mount(
|
||||
Mount::bind_mount(
|
||||
self.agent_config.nginx_config_dir.clone(),
|
||||
NGINX_CONFIG_DIR.to_string(),
|
||||
)
|
||||
.with_access_mode(AccessMode::ReadWrite),
|
||||
)
|
||||
.start())
|
||||
}
|
||||
}
|
||||
57
apps/container/src/containers/db.rs
Normal file
57
apps/container/src/containers/db.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
pub mod config;
|
||||
pub mod postgresql;
|
||||
pub mod sqlite;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use shared::db_type::DBType;
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
use url::Host;
|
||||
|
||||
use testcontainers::{ContainerAsync, GenericImage};
|
||||
|
||||
use crate::{
|
||||
WithContainer, WithoutContainer,
|
||||
containers::{DBConfigInfoType, UnStartedContainer},
|
||||
};
|
||||
|
||||
#[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, Box<dyn Error>>;
|
||||
}
|
||||
41
apps/container/src/containers/db/config.rs
Normal file
41
apps/container/src/containers/db/config.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
#[derive(Default)]
|
||||
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 DatabaseContainerConfig {
|
||||
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: &DatabaseContainerConfig) -> DatabaseContainerConfig {
|
||||
DatabaseContainerConfig {
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
98
apps/container/src/containers/db/postgresql.rs
Normal file
98
apps/container/src/containers/db/postgresql.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use std::{error::Error, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use testcontainers::{
|
||||
GenericImage, ImageExt,
|
||||
core::{IntoContainerPort, WaitFor},
|
||||
runners::AsyncRunner,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ConfigInfoType,
|
||||
containers::{
|
||||
UnStartedContainer,
|
||||
db::{
|
||||
ContainerizedDBInfo, DBConfigInfoType, DBInfo,
|
||||
config::{DatabaseContainerConfig, OptionalContainerConfig},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub fn get_default_config() -> DatabaseContainerConfig {
|
||||
DatabaseContainerConfig {
|
||||
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: DatabaseContainerConfig,
|
||||
}
|
||||
|
||||
#[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::containers::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, Box<dyn Error>> {
|
||||
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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
107
apps/container/src/containers/db/sqlite.rs
Normal file
107
apps/container/src/containers/db/sqlite.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use std::{error::Error, path::PathBuf, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::{
|
||||
ConfigInfoType,
|
||||
containers::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,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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!("sqlite://{}", 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::containers::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()
|
||||
&& 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, Box<dyn Error>> {
|
||||
Err(Box::new(std::io::Error::other(
|
||||
"SQLite does not use a container",
|
||||
)))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user