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:
63
seeds/postgres/dev_seed.sql
Normal file
63
seeds/postgres/dev_seed.sql
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
-- dev_seed.sql (PostgreSQL) — development test data
|
||||||
|
--
|
||||||
|
-- Running this script is idempotent: existing rows are left untouched
|
||||||
|
-- and missing rows are inserted. Safe to run multiple times.
|
||||||
|
--
|
||||||
|
-- Load with: cargo run --features ssr --bin seed
|
||||||
|
|
||||||
|
-- ── Networks ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
INSERT INTO networks (cidr) VALUES
|
||||||
|
('192.168.1.0/24'),
|
||||||
|
('192.168.10.0/24'),
|
||||||
|
('10.0.0.0/8'),
|
||||||
|
('172.16.0.0/16')
|
||||||
|
ON CONFLICT (cidr) DO NOTHING;
|
||||||
|
|
||||||
|
-- ── Hosts ─────────────────────────────────────────────────────────────────────
|
||||||
|
-- Hosts have no UNIQUE constraint, so we guard with WHERE NOT EXISTS.
|
||||||
|
|
||||||
|
-- LAN — 192.168.1.0/24
|
||||||
|
INSERT INTO hosts (name, ip, network_id)
|
||||||
|
SELECT name, ip, (SELECT id FROM networks WHERE cidr = '192.168.1.0/24')
|
||||||
|
FROM (VALUES
|
||||||
|
('gateway', '192.168.1.1'),
|
||||||
|
('workstation-01', '192.168.1.10'),
|
||||||
|
('workstation-02', '192.168.1.11'),
|
||||||
|
('workstation-03', '192.168.1.12'),
|
||||||
|
('nas-01', '192.168.1.20'),
|
||||||
|
('printer-01', '192.168.1.50')
|
||||||
|
) AS t(name, ip)
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM hosts WHERE hosts.name = t.name AND hosts.ip = t.ip);
|
||||||
|
|
||||||
|
-- DMZ — 192.168.10.0/24
|
||||||
|
INSERT INTO hosts (name, ip, network_id)
|
||||||
|
SELECT name, ip, (SELECT id FROM networks WHERE cidr = '192.168.10.0/24')
|
||||||
|
FROM (VALUES
|
||||||
|
('web-server-01', '192.168.10.10'),
|
||||||
|
('web-server-02', '192.168.10.11'),
|
||||||
|
('db-server-01', '192.168.10.20'),
|
||||||
|
('mail-server-01', '192.168.10.30')
|
||||||
|
) AS t(name, ip)
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM hosts WHERE hosts.name = t.name AND hosts.ip = t.ip);
|
||||||
|
|
||||||
|
-- Corporate backbone — 10.0.0.0/8
|
||||||
|
INSERT INTO hosts (name, ip, network_id)
|
||||||
|
SELECT name, ip, (SELECT id FROM networks WHERE cidr = '10.0.0.0/8')
|
||||||
|
FROM (VALUES
|
||||||
|
('core-switch-01', '10.0.0.1'),
|
||||||
|
('monitoring-01', '10.0.1.10'),
|
||||||
|
('backup-server-01', '10.0.1.20'),
|
||||||
|
('log-server-01', '10.0.1.30')
|
||||||
|
) AS t(name, ip)
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM hosts WHERE hosts.name = t.name AND hosts.ip = t.ip);
|
||||||
|
|
||||||
|
-- VPN — 172.16.0.0/16
|
||||||
|
INSERT INTO hosts (name, ip, network_id)
|
||||||
|
SELECT name, ip, (SELECT id FROM networks WHERE cidr = '172.16.0.0/16')
|
||||||
|
FROM (VALUES
|
||||||
|
('vpn-gateway-01', '172.16.0.1'),
|
||||||
|
('vpn-client-01', '172.16.1.10'),
|
||||||
|
('vpn-client-02', '172.16.1.11')
|
||||||
|
) AS t(name, ip)
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM hosts WHERE hosts.name = t.name AND hosts.ip = t.ip);
|
||||||
43
seeds/sqlite/dev_seed.sql
Normal file
43
seeds/sqlite/dev_seed.sql
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
-- dev_seed.sql (SQLite) — development test data
|
||||||
|
--
|
||||||
|
-- Running this script is idempotent: existing rows are left untouched
|
||||||
|
-- and missing rows are inserted. Safe to run multiple times.
|
||||||
|
--
|
||||||
|
-- Load with: cargo run --features ssr --bin seed
|
||||||
|
|
||||||
|
-- ── Networks ──────────────────────────────────────────────────────────────────
|
||||||
|
-- INSERT OR IGNORE relies on the UNIQUE constraint on cidr.
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO networks (cidr) VALUES ('192.168.1.0/24');
|
||||||
|
INSERT OR IGNORE INTO networks (cidr) VALUES ('192.168.10.0/24');
|
||||||
|
INSERT OR IGNORE INTO networks (cidr) VALUES ('10.0.0.0/8');
|
||||||
|
INSERT OR IGNORE INTO networks (cidr) VALUES ('172.16.0.0/16');
|
||||||
|
|
||||||
|
-- ── Hosts ─────────────────────────────────────────────────────────────────────
|
||||||
|
-- Hosts have no UNIQUE constraint, so we guard each insert with WHERE NOT EXISTS.
|
||||||
|
-- Network IDs are resolved by subquery on cidr for portability.
|
||||||
|
|
||||||
|
-- LAN — 192.168.1.0/24
|
||||||
|
INSERT INTO hosts (name, ip, network_id) SELECT 'gateway', '192.168.1.1', id FROM networks WHERE cidr = '192.168.1.0/24' AND NOT EXISTS (SELECT 1 FROM hosts WHERE name = 'gateway' AND ip = '192.168.1.1');
|
||||||
|
INSERT INTO hosts (name, ip, network_id) SELECT 'workstation-01', '192.168.1.10', id FROM networks WHERE cidr = '192.168.1.0/24' AND NOT EXISTS (SELECT 1 FROM hosts WHERE name = 'workstation-01' AND ip = '192.168.1.10');
|
||||||
|
INSERT INTO hosts (name, ip, network_id) SELECT 'workstation-02', '192.168.1.11', id FROM networks WHERE cidr = '192.168.1.0/24' AND NOT EXISTS (SELECT 1 FROM hosts WHERE name = 'workstation-02' AND ip = '192.168.1.11');
|
||||||
|
INSERT INTO hosts (name, ip, network_id) SELECT 'workstation-03', '192.168.1.12', id FROM networks WHERE cidr = '192.168.1.0/24' AND NOT EXISTS (SELECT 1 FROM hosts WHERE name = 'workstation-03' AND ip = '192.168.1.12');
|
||||||
|
INSERT INTO hosts (name, ip, network_id) SELECT 'nas-01', '192.168.1.20', id FROM networks WHERE cidr = '192.168.1.0/24' AND NOT EXISTS (SELECT 1 FROM hosts WHERE name = 'nas-01' AND ip = '192.168.1.20');
|
||||||
|
INSERT INTO hosts (name, ip, network_id) SELECT 'printer-01', '192.168.1.50', id FROM networks WHERE cidr = '192.168.1.0/24' AND NOT EXISTS (SELECT 1 FROM hosts WHERE name = 'printer-01' AND ip = '192.168.1.50');
|
||||||
|
|
||||||
|
-- DMZ — 192.168.10.0/24
|
||||||
|
INSERT INTO hosts (name, ip, network_id) SELECT 'web-server-01', '192.168.10.10', id FROM networks WHERE cidr = '192.168.10.0/24' AND NOT EXISTS (SELECT 1 FROM hosts WHERE name = 'web-server-01' AND ip = '192.168.10.10');
|
||||||
|
INSERT INTO hosts (name, ip, network_id) SELECT 'web-server-02', '192.168.10.11', id FROM networks WHERE cidr = '192.168.10.0/24' AND NOT EXISTS (SELECT 1 FROM hosts WHERE name = 'web-server-02' AND ip = '192.168.10.11');
|
||||||
|
INSERT INTO hosts (name, ip, network_id) SELECT 'db-server-01', '192.168.10.20', id FROM networks WHERE cidr = '192.168.10.0/24' AND NOT EXISTS (SELECT 1 FROM hosts WHERE name = 'db-server-01' AND ip = '192.168.10.20');
|
||||||
|
INSERT INTO hosts (name, ip, network_id) SELECT 'mail-server-01', '192.168.10.30', id FROM networks WHERE cidr = '192.168.10.0/24' AND NOT EXISTS (SELECT 1 FROM hosts WHERE name = 'mail-server-01' AND ip = '192.168.10.30');
|
||||||
|
|
||||||
|
-- Corporate backbone — 10.0.0.0/8
|
||||||
|
INSERT INTO hosts (name, ip, network_id) SELECT 'core-switch-01', '10.0.0.1', id FROM networks WHERE cidr = '10.0.0.0/8' AND NOT EXISTS (SELECT 1 FROM hosts WHERE name = 'core-switch-01' AND ip = '10.0.0.1');
|
||||||
|
INSERT INTO hosts (name, ip, network_id) SELECT 'monitoring-01', '10.0.1.10', id FROM networks WHERE cidr = '10.0.0.0/8' AND NOT EXISTS (SELECT 1 FROM hosts WHERE name = 'monitoring-01' AND ip = '10.0.1.10');
|
||||||
|
INSERT INTO hosts (name, ip, network_id) SELECT 'backup-server-01', '10.0.1.20', id FROM networks WHERE cidr = '10.0.0.0/8' AND NOT EXISTS (SELECT 1 FROM hosts WHERE name = 'backup-server-01' AND ip = '10.0.1.20');
|
||||||
|
INSERT INTO hosts (name, ip, network_id) SELECT 'log-server-01', '10.0.1.30', id FROM networks WHERE cidr = '10.0.0.0/8' AND NOT EXISTS (SELECT 1 FROM hosts WHERE name = 'log-server-01' AND ip = '10.0.1.30');
|
||||||
|
|
||||||
|
-- VPN — 172.16.0.0/16
|
||||||
|
INSERT INTO hosts (name, ip, network_id) SELECT 'vpn-gateway-01', '172.16.0.1', id FROM networks WHERE cidr = '172.16.0.0/16' AND NOT EXISTS (SELECT 1 FROM hosts WHERE name = 'vpn-gateway-01' AND ip = '172.16.0.1');
|
||||||
|
INSERT INTO hosts (name, ip, network_id) SELECT 'vpn-client-01', '172.16.1.10', id FROM networks WHERE cidr = '172.16.0.0/16' AND NOT EXISTS (SELECT 1 FROM hosts WHERE name = 'vpn-client-01' AND ip = '172.16.1.10');
|
||||||
|
INSERT INTO hosts (name, ip, network_id) SELECT 'vpn-client-02', '172.16.1.11', id FROM networks WHERE cidr = '172.16.0.0/16' AND NOT EXISTS (SELECT 1 FROM hosts WHERE name = 'vpn-client-02' AND ip = '172.16.1.11');
|
||||||
89
src/bin/seed.rs
Normal file
89
src/bin/seed.rs
Normal 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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user