feat(seed): provision postgres role + KV slot for headscale

Adds `headscale` to the lists that drive the seed workflow so the
existing CNPG role/database creation and OpenBao KV path provisioning
pick up the new VPN coordination service alongside everything else:

- src/secrets.rs: PG_USERS list grows from 15 → 16 (test asserts the
  full ordered list, so it's updated to match)
- src/workflows/seed/steps/postgres.rs: pg_db_map adds headscale →
  headscale_db
- src/workflows/seed/definition.rs: bumps the role/db step count
  assertions from 15 → 16
- src/workflows/primitives/kv_service_configs.rs: new headscale entry
  with a single `api-key` field generated as `static:` (placeholder).
  The user runs `kubectl exec -n vpn deploy/headscale -- headscale
  apikeys create` and pastes the result into vault before calling
  `sunbeam vpn create-key`. Bumps service_count test from 18 → 19.
- src/constants.rs: add `vpn` to MANAGED_NS so the legacy namespace
  list includes the new namespace.
This commit is contained in:
2026-04-07 17:35:17 +01:00
parent b5795fd97b
commit 5f97d063cb
5 changed files with 21 additions and 8 deletions

View File

@@ -2,6 +2,7 @@
pub const GITEA_ADMIN_USER: &str = "gitea_admin";
/// Deprecated: prefer `registry::discover()` → `ServiceRegistry::namespaces()`.
pub const MANAGED_NS: &[&str] = &[
"data",
"devtools",
@@ -13,4 +14,5 @@ pub const MANAGED_NS: &[&str] = &[
"ory",
"storage",
"vault-secrets-operator",
"vpn",
];

View File

@@ -37,6 +37,7 @@ pub(crate) const PG_USERS: &[&str] = &[
"projects",
"penpot",
"stalwart",
"headscale",
];
pub(crate) const SMTP_URI: &str = "smtp://postfix.lasuite.svc.cluster.local:25/?skip_ssl_verify=true";
@@ -468,7 +469,7 @@ mod tests {
fn test_constants() {
assert_eq!(ADMIN_USERNAME, "estudio-admin");
assert_eq!(GITEA_ADMIN_USER, "gitea_admin");
assert_eq!(PG_USERS.len(), 15);
assert_eq!(PG_USERS.len(), 16);
assert!(PG_USERS.contains(&"kratos"));
assert!(PG_USERS.contains(&"projects"));
}
@@ -518,6 +519,7 @@ mod tests {
"projects",
"penpot",
"stalwart",
"headscale",
];
assert_eq!(PG_USERS, &expected[..]);
}

View File

@@ -91,6 +91,14 @@ pub fn all_service_configs() -> Vec<Value> {
{"key":"access-key-id","generator":"scw_config_access"},
{"key":"secret-access-key","generator":"scw_config_secret"}
]}),
// Headscale API key — generated *out of band* via
// `kubectl exec -n vpn deploy/headscale -- headscale apikeys create`
// and pasted into vault. Seed leaves a placeholder slot so the
// path exists; replace with the real key before running
// `sunbeam vpn create-key`.
json!({"service":"headscale","fields":[
{"key":"api-key","generator":"static:"}
]}),
]
}
@@ -112,7 +120,7 @@ pub fn all_service_names() -> Vec<&'static str> {
"hydra", "kratos", "seaweedfs", "gitea", "hive", "livekit",
"people", "login-ui", "kratos-admin", "docs", "meet", "drive",
"projects", "calendars", "messages", "collabora", "tuwunel",
"grafana", "scaleway-s3",
"grafana", "scaleway-s3", "headscale",
]
}
@@ -130,9 +138,9 @@ mod tests {
#[test]
fn service_count() {
// 18 independent + 1 kratos-admin (dependent)
assert_eq!(all_service_configs().len(), 18);
assert_eq!(all_service_names().len(), 19);
// 19 independent + 1 kratos-admin (dependent)
assert_eq!(all_service_configs().len(), 19);
assert_eq!(all_service_names().len(), 20);
}
#[test]

View File

@@ -206,8 +206,8 @@ mod tests {
let db_steps: Vec<_> = def.steps.iter()
.filter(|s| s.step_type.contains("CreatePGDatabase"))
.collect();
assert_eq!(role_steps.len(), 15, "should have 15 CreatePGRole steps");
assert_eq!(db_steps.len(), 15, "should have 15 CreatePGDatabase steps");
assert_eq!(role_steps.len(), 16, "should have 16 CreatePGRole steps");
assert_eq!(db_steps.len(), 16, "should have 16 CreatePGDatabase steps");
}
#[test]

View File

@@ -39,6 +39,7 @@ pub(crate) fn pg_db_map() -> HashMap<&'static str, &'static str> {
("find", "find_db"), ("calendars", "calendars_db"), ("projects", "projects_db"),
("penpot", "penpot_db"),
("stalwart", "stalwart_db"),
("headscale", "headscale_db"),
].into_iter().collect()
}
@@ -218,7 +219,7 @@ mod tests {
#[test]
fn test_pg_db_map_contains_all_users() {
let map = pg_db_map();
assert_eq!(map.len(), 15);
assert_eq!(map.len(), 16);
for user in crate::secrets::PG_USERS {
assert!(map.contains_key(user), "pg_db_map missing key for: {user}");
}