use lmrc_config_validator::LmrcConfig;
use lmrc_toml_writer::WorkspaceToml;
use std::fs;
use std::path::Path;
use crate::error::Result;
include!(concat!(env!("OUT_DIR"), "/versions.rs"));
pub fn generate_workspace_toml(project_path: &Path, config: &LmrcConfig) -> Result<()> {
let mut builder = WorkspaceToml::new().resolver("2");
if project_path.join("infra/pipeline/Cargo.toml").exists() {
builder = builder.member("infra/pipeline");
}
for app in ["gateway", "infra-api", "infra-migrator", "app-migrator"] {
if project_path
.join(format!("apps/{}/Cargo.toml", app))
.exists()
{
builder = builder.member(format!("apps/{}", app));
}
}
for app in &config.apps.applications {
if matches!(
app.app_type,
Some(lmrc_config_validator::AppType::Gateway)
| Some(lmrc_config_validator::AppType::Migrator)
) {
continue;
}
builder = builder.member(format!("apps/{}", app.name));
}
let authors = config
.infrastructure
.gitlab
.as_ref()
.map(|g| format!("{} Team", g.namespace))
.unwrap_or_else(|| "LMRC Team".to_string());
let repository = config
.infrastructure
.gitlab
.as_ref()
.map(|g| format!("{}/{}/{}", g.url, g.namespace, config.project.name))
.unwrap_or_else(|| "https://example.com/repo".to_string());
builder = builder
.workspace_version("0.1.0")
.workspace_edition("2021")
.workspace_author(authors)
.workspace_license("MIT OR Apache-2.0")
.workspace_repository(repository);
builder = builder
.dependency("lmrc-gitlab", LMRC_GITLAB_VERSION)
.dependency("lmrc-hetzner", LMRC_HETZNER_VERSION)
.dependency("lmrc-k3s", LMRC_K3S_VERSION)
.dependency("lmrc-cloudflare", LMRC_CLOUDFLARE_VERSION)
.dependency("lmrc-postgres", LMRC_POSTGRES_VERSION)
.dependency("lmrc-ssh", LMRC_SSH_VERSION)
.dependency("lmrc-docker", LMRC_DOCKER_VERSION)
.dependency("lmrc-kubernetes", LMRC_KUBERNETES_VERSION)
.dependency("lmrc-pipeline", LMRC_PIPELINE_VERSION)
.dependency("lmrc-http-common", LMRC_HTTP_COMMON_VERSION)
.dependency("lmrc-config-validator", LMRC_CONFIG_VALIDATOR_VERSION);
builder = builder
.dependency("tokio", r#"{ version = "1.0", features = ["full"] }"#)
.dependency("serde", r#"{ version = "1.0", features = ["derive"] }"#)
.dependency("serde_json", "1.0")
.dependency("thiserror", "2.0")
.dependency("tracing", "0.1")
.dependency("tracing-subscriber", "0.3")
.dependency("clap", r#"{ version = "4.5", features = ["derive"] }"#)
.dependency("toml", "0.8")
.dependency("axum", "0.7")
.dependency("tower", "0.5")
.dependency("tower-http", r#"{ version = "0.6", features = ["trace", "cors", "request-id", "util"] }"#)
.dependency("hyper", r#"{ version = "1.0", features = ["full"] }"#)
.dependency("hyper-util", r#"{ version = "0.1", features = ["tokio"] }"#)
.dependency("sea-orm", r#"{ version = "1.1", features = ["sqlx-postgres", "runtime-tokio-rustls", "macros"] }"#)
.dependency("sea-orm-migration", "1.1")
.dependency("jsonwebtoken", "9.3")
.dependency("bcrypt", "0.16")
.dependency("cookie", "0.18")
.dependency("chrono", r#"{ version = "0.4", features = ["serde"] }"#)
.dependency("time", "0.3")
.dependency("dotenvy", "0.15")
.dependency("validator", r#"{ version = "0.18", features = ["derive"] }"#)
.dependency("uuid", r#"{ version = "1.0", features = ["v4"] }"#)
.dependency("anyhow", "1.0")
.dependency("async-trait", "0.1")
.dependency("reqwest", r#"{ version = "0.12", default-features = false, features = ["rustls-tls", "json"] }"#)
;
let workspace_toml = builder.build();
let cargo_path = project_path.join("Cargo.toml");
fs::write(&cargo_path, workspace_toml)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use lmrc_config_validator::*;
use std::collections::HashMap;
use tempfile::TempDir;
fn create_test_config() -> LmrcConfig {
LmrcConfig {
project: ProjectConfig {
name: "test-project".to_string(),
description: "Test project".to_string(),
},
providers: ProviderConfig {
server: "hetzner".to_string(),
kubernetes: "k3s".to_string(),
database: "postgres".to_string(),
queue: "rabbitmq".to_string(),
dns: "cloudflare".to_string(),
git: "gitlab".to_string(),
},
apps: AppsConfig {
applications: vec![
ApplicationEntry {
name: "app1".to_string(),
app_type: Some(lmrc_config_validator::AppType::Api),
docker: None,
deployment: None,
},
ApplicationEntry {
name: "app2".to_string(),
app_type: Some(lmrc_config_validator::AppType::Basic),
docker: None,
deployment: None,
},
],
},
infrastructure: InfrastructureConfig {
provider: "hetzner".to_string(),
network: None,
servers: vec![ServerGroup {
name: "k3s-server".to_string(),
role: ServerRole::K3sControl,
server_type: "cx11".to_string(),
location: "nbg1".to_string(),
count: 1,
labels: HashMap::new(),
ssh_keys: vec![],
image: None,
}],
k3s: Some(K3sConfig {
version: "v1.28.5+k3s1".to_string(),
deploy_on: vec!["k3s-server".to_string()],
control_plane_servers: vec!["k3s-server".to_string()],
worker_servers: vec![],
enable_traefik: true,
enable_metrics_server: false,
server_flags: vec![],
agent_flags: vec![],
}),
postgres: Some(PostgresConfig {
version: "16".to_string(),
database_name: "testdb".to_string(),
deployment_mode: PostgresDeploymentMode::InCluster,
standalone: None,
in_cluster: Some(PostgresInClusterConfig {
namespace: "default".to_string(),
storage_class: "local-path".to_string(),
storage_size: "10Gi".to_string(),
use_operator: false,
}),
}),
rabbitmq: None,
vault: None,
dns: Some(DnsConfig {
provider: "cloudflare".to_string(),
domain: "test.example.com".to_string(),
records: vec![],
}),
gitlab: Some(GitLabConfig {
url: "https://gitlab.com".to_string(),
namespace: "testuser".to_string(),
}),
load_balancer: None,
},
}
}
#[test]
fn test_generate_workspace_toml_creates_file() {
let temp_dir = TempDir::new().unwrap();
let config = create_test_config();
let result = generate_workspace_toml(temp_dir.path(), &config);
assert!(result.is_ok());
let cargo_toml = temp_dir.path().join("Cargo.toml");
assert!(cargo_toml.exists());
}
#[test]
fn test_workspace_toml_has_workspace_section() {
let temp_dir = TempDir::new().unwrap();
let config = create_test_config();
generate_workspace_toml(temp_dir.path(), &config).unwrap();
let cargo_toml = temp_dir.path().join("Cargo.toml");
let content = fs::read_to_string(cargo_toml).unwrap();
assert!(content.contains("[workspace]"));
}
#[test]
fn test_workspace_toml_has_correct_resolver() {
let temp_dir = TempDir::new().unwrap();
let config = create_test_config();
generate_workspace_toml(temp_dir.path(), &config).unwrap();
let cargo_toml = temp_dir.path().join("Cargo.toml");
let content = fs::read_to_string(cargo_toml).unwrap();
assert!(content.contains("resolver = \"2\""));
}
#[test]
fn test_workspace_toml_includes_all_app_members() {
let temp_dir = TempDir::new().unwrap();
let config = create_test_config();
generate_workspace_toml(temp_dir.path(), &config).unwrap();
let cargo_toml = temp_dir.path().join("Cargo.toml");
let content = fs::read_to_string(cargo_toml).unwrap();
assert!(content.contains("\"apps/app1\""));
assert!(content.contains("\"apps/app2\""));
}
#[test]
fn test_workspace_toml_includes_pipeline_member() {
let temp_dir = TempDir::new().unwrap();
let config = create_test_config();
let pipeline_dir = temp_dir.path().join("infra/pipeline");
fs::create_dir_all(&pipeline_dir).unwrap();
fs::write(
pipeline_dir.join("Cargo.toml"),
"[package]\nname = \"pipeline\"\n",
)
.unwrap();
generate_workspace_toml(temp_dir.path(), &config).unwrap();
let cargo_toml = temp_dir.path().join("Cargo.toml");
let content = fs::read_to_string(cargo_toml).unwrap();
assert!(content.contains("\"infra/pipeline\""));
}
#[test]
fn test_workspace_toml_includes_lmrc_dependencies() {
let temp_dir = TempDir::new().unwrap();
let config = create_test_config();
generate_workspace_toml(temp_dir.path(), &config).unwrap();
let cargo_toml = temp_dir.path().join("Cargo.toml");
let content = fs::read_to_string(cargo_toml).unwrap();
assert!(content.contains("[workspace.dependencies]"));
let lmrc_deps = [
"lmrc-gitlab",
"lmrc-hetzner",
"lmrc-k3s",
"lmrc-cloudflare",
"lmrc-postgres",
"lmrc-ssh",
"lmrc-docker",
"lmrc-kubernetes",
"lmrc-pipeline",
];
for dep in &lmrc_deps {
assert!(
content.contains(dep),
"Missing workspace dependency: {}",
dep
);
}
}
#[test]
fn test_workspace_toml_includes_common_dependencies() {
let temp_dir = TempDir::new().unwrap();
let config = create_test_config();
generate_workspace_toml(temp_dir.path(), &config).unwrap();
let cargo_toml = temp_dir.path().join("Cargo.toml");
let content = fs::read_to_string(cargo_toml).unwrap();
let common_deps = [
"tokio",
"serde",
"serde_json",
"thiserror",
"tracing",
"clap",
"toml",
];
for dep in &common_deps {
assert!(content.contains(dep), "Missing common dependency: {}", dep);
}
}
#[test]
fn test_workspace_toml_with_single_app() {
let temp_dir = TempDir::new().unwrap();
let mut config = create_test_config();
config.apps.applications = vec![ApplicationEntry {
name: "single-app".to_string(),
app_type: Some(lmrc_config_validator::AppType::Api),
docker: None,
deployment: None,
}];
generate_workspace_toml(temp_dir.path(), &config).unwrap();
let cargo_toml = temp_dir.path().join("Cargo.toml");
let content = fs::read_to_string(cargo_toml).unwrap();
assert!(content.contains("\"apps/single-app\""));
assert!(!content.contains("\"apps/app1\""));
}
#[test]
fn test_workspace_toml_sets_workspace_metadata() {
let temp_dir = TempDir::new().unwrap();
let config = create_test_config();
generate_workspace_toml(temp_dir.path(), &config).unwrap();
let cargo_toml = temp_dir.path().join("Cargo.toml");
let content = fs::read_to_string(cargo_toml).unwrap();
assert!(content.contains("[workspace.package]"));
assert!(content.contains("version = \"0.1.0\""));
assert!(content.contains("edition = \"2021\""));
}
#[test]
fn test_workspace_toml_author_from_gitlab_namespace() {
let temp_dir = TempDir::new().unwrap();
let config = create_test_config();
generate_workspace_toml(temp_dir.path(), &config).unwrap();
let cargo_toml = temp_dir.path().join("Cargo.toml");
let content = fs::read_to_string(cargo_toml).unwrap();
assert!(content.contains("testuser Team"));
}
}