feat(seed): add dev seed binary with networks and hosts

Creates a self-contained `seed` binary (cargo run --features ssr --bin seed)
that loads realistic test data into the database. Idempotent: safe to run
multiple times without creating duplicates.

Data: 4 networks (LAN, DMZ, corporate, VPN) and 17 hosts spread across them.
Both SQLite and PostgreSQL seed files are provided.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 01:33:21 +02:00
parent 55d8ed9f72
commit e17b8ee722
3 changed files with 195 additions and 0 deletions

89
src/bin/seed.rs Normal file
View File

@@ -0,0 +1,89 @@
// bin/seed.rs — Development seed loader
//
// Inserts a realistic set of networks and hosts into the database so the UI
// can be tested without manual data entry.
//
// The seed is idempotent: running it multiple times never duplicates rows.
//
// Usage:
// cargo run --features ssr --bin seed
//
// The DATABASE_URL is read from the .env file (or environment variable),
// exactly like the main server.
#[tokio::main]
async fn main() {
use rust_ipam::server::{
config::{AppConfig, DatabaseBackend},
db::{create_pool, run_migrations},
};
// Load .env so DATABASE_URL is available without exporting it manually.
// Errors are ignored: the variable may already be set in the environment.
let _ = dotenvy::dotenv();
tracing_subscriber::fmt()
.with_env_filter(
std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()),
)
.init();
let config = AppConfig::from_env()
.expect("Configuration error — check DATABASE_URL in your .env file");
tracing::info!("Connecting to {} ({})", config.backend, config.database_url);
let pool = create_pool(&config)
.await
.expect("Failed to connect to database");
run_migrations(&pool, &config.backend)
.await
.expect("Migration failed");
// Pick the seed file that matches the active backend.
// `include_str!` embeds the SQL at compile time so the binary is self-contained.
let sql = match config.backend {
DatabaseBackend::Sqlite => include_str!("../../seeds/sqlite/dev_seed.sql"),
DatabaseBackend::Postgres => include_str!("../../seeds/postgres/dev_seed.sql"),
};
// Strip comment lines first, then split on ';'.
// sqlx does not support multiple statements in a single `query()` call.
// Without pre-stripping comments, a block like "-- section\nINSERT …"
// would start with "--" and get incorrectly discarded.
let sql_stripped: String = sql
.lines()
.filter(|line| !line.trim().starts_with("--"))
.collect::<Vec<_>>()
.join("\n");
let statements: Vec<String> = sql_stripped
.split(';')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
let total = statements.len();
for (i, stmt) in statements.iter().enumerate() {
sqlx::query(stmt)
.execute(&pool)
.await
.unwrap_or_else(|e| panic!("Statement {}/{} failed: {}\nSQL: {}", i + 1, total, e, stmt));
}
tracing::info!("Seed complete — {} statement(s) executed.", total);
// Count what was inserted so the operator can confirm at a glance.
let network_count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM networks")
.fetch_one(&pool)
.await
.unwrap_or(0);
let host_count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM hosts")
.fetch_one(&pool)
.await
.unwrap_or(0);
tracing::info!("Database now contains {} network(s) and {} host(s).", network_count, host_count);
}