hive-sh4re: type infra containers as an InfraContainer enum
Replace the stringly-typed infra-control path with an InfraContainer enum
(Ci/Forge/Gateway/Matrix). The variants are the allowlist: serde rejects any
unknown or unsafe name (hive-c0re has no variant) at the wire boundary, so
hive-priv no longer needs a root-side SIBLING_CONTAINERS.contains() check on
ControlInfraContainer — the type enforces it, and 'the daemon can't stop
itself' is a compile-time guarantee.
- priv_proto: InfraContainer enum; manual Serialize/Deserialize + FromStr +
unit_name() all key off one mapping, so the wire form ('hive-ci', …) is
unchanged and there's no drift. ControlInfraContainer.container: String ->
InfraContainer.
- hive-priv / priv_client / server.rs: thread the enum; scoped_infra returns
Vec<InfraContainer>; the control handler uses unit_name().
- agent_server: the infra_admin restart gate parses the name via FromStr
instead of a slice .contains().
- SIBLING_CONTAINERS stays (validate_container_name/_system_name still use it
for journals / general container validation); a test keeps the enum and the
slice in lockstep.
This commit is contained in:
parent
6b1dbebe5a
commit
cd025b3790
5 changed files with 146 additions and 71 deletions
|
|
@ -46,6 +46,59 @@ impl InfraAction {
|
|||
}
|
||||
}
|
||||
|
||||
/// A hive infrastructure container that can be controlled (start / stop /
|
||||
/// restart) via [`PrivRequest::ControlInfraContainer`]. The variants ARE
|
||||
/// the allowlist: serde rejects any other value at the wire boundary, so an
|
||||
/// unknown or unsafe target — notably `hive-c0re`, which has no variant and
|
||||
/// would sever the daemon socket — is *unrepresentable* rather than caught
|
||||
/// by a runtime check. The c0re↔hive-priv wire form uses serde's default
|
||||
/// variant naming (`"Ci"`, `"Forge"`, …); it's an internal protocol (both
|
||||
/// ends rebuild together) so it needn't match the container name.
|
||||
/// [`unit_name`](Self::unit_name) is the separate systemd / container name
|
||||
/// (`hive-ci`).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum InfraContainer {
|
||||
Ci,
|
||||
Forge,
|
||||
Gateway,
|
||||
Matrix,
|
||||
}
|
||||
|
||||
impl InfraContainer {
|
||||
/// Every controllable infra container. The source of truth that
|
||||
/// [`SIBLING_CONTAINERS`] is kept consistent with (see the test).
|
||||
pub const ALL: [InfraContainer; 4] = [
|
||||
InfraContainer::Ci,
|
||||
InfraContainer::Forge,
|
||||
InfraContainer::Gateway,
|
||||
InfraContainer::Matrix,
|
||||
];
|
||||
|
||||
/// The container / systemd-unit name, e.g. `hive-ci` →
|
||||
/// `container@hive-ci.service`. (Distinct from the serde wire form,
|
||||
/// which is the default variant name `"Ci"`.)
|
||||
#[must_use]
|
||||
pub fn unit_name(self) -> &'static str {
|
||||
match self {
|
||||
InfraContainer::Ci => "hive-ci",
|
||||
InfraContainer::Forge => "hive-forge",
|
||||
InfraContainer::Gateway => "hive-gateway",
|
||||
InfraContainer::Matrix => "hive-matrix",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for InfraContainer {
|
||||
type Err = ();
|
||||
|
||||
/// Parse a container name (`hive-ci`, …) into a variant. Used to decide
|
||||
/// whether an MCP `restart(<name>)` target is a controllable infra
|
||||
/// container. `Err(())` for anything that isn't one.
|
||||
fn from_str(s: &str) -> Result<Self, ()> {
|
||||
Self::ALL.into_iter().find(|c| c.unit_name() == s).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Host path of the meta flake. The flake ref for agent `<name>` is
|
||||
/// `{META_DIR}#{name}`, derived by `hive-priv` — never passed over the wire.
|
||||
pub const META_DIR: &str = "/var/lib/hyperhive/meta";
|
||||
|
|
@ -325,15 +378,14 @@ pub enum PrivRequest {
|
|||
},
|
||||
|
||||
/// Start / stop / restart a hive infrastructure container on the host
|
||||
/// via `systemctl <action> container@<container>.service`. hive-priv
|
||||
/// validates `container` against [`SIBLING_CONTAINERS`] root-side (the
|
||||
/// authoritative allowlist; `hive-c0re` is never in it). Serves both the
|
||||
/// hive-wide `hivectl stop` / `hivectl start` flow and an `infra_admin`
|
||||
/// agent's `restart` (with `action = Restart`).
|
||||
/// via `systemctl <action> container@<container>.service`. The
|
||||
/// [`InfraContainer`] enum is the allowlist — serde rejects unknown /
|
||||
/// unsafe names (notably `hive-c0re`, which has no variant) at the wire
|
||||
/// boundary, so no root-side `.contains()` check is needed. Serves both
|
||||
/// the hive-wide `hivectl stop` / `hivectl start` flow and an
|
||||
/// `infra_admin` agent's `restart` (with `action = Restart`).
|
||||
ControlInfraContainer {
|
||||
/// Infra container name (e.g. `hive-ci`); must be in
|
||||
/// [`SIBLING_CONTAINERS`].
|
||||
container: String,
|
||||
container: InfraContainer,
|
||||
action: InfraAction,
|
||||
},
|
||||
|
||||
|
|
@ -486,7 +538,7 @@ pub enum PrivEvent {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SIBLING_CONTAINERS;
|
||||
use super::{InfraContainer, SIBLING_CONTAINERS};
|
||||
|
||||
#[test]
|
||||
fn infra_control_allowlist_excludes_c0re_includes_matrix() {
|
||||
|
|
@ -497,4 +549,29 @@ mod tests {
|
|||
// hive-matrix IS controllable (operator can stop/start/restart it).
|
||||
assert!(SIBLING_CONTAINERS.contains(&"hive-matrix"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infra_container_enum_matches_sibling_containers() {
|
||||
// The InfraContainer enum (the control-path allowlist) and the
|
||||
// SIBLING_CONTAINERS slice (the general container-name validator)
|
||||
// must list exactly the same four containers — they're separate
|
||||
// surfaces for the same set, so keep them in lockstep.
|
||||
let mut from_enum: Vec<&str> = InfraContainer::ALL.iter().map(|c| c.unit_name()).collect();
|
||||
from_enum.sort_unstable();
|
||||
let mut from_slice: Vec<&str> = SIBLING_CONTAINERS.to_vec();
|
||||
from_slice.sort_unstable();
|
||||
assert_eq!(from_enum, from_slice);
|
||||
// hive-c0re has no variant — unrepresentable, can't be controlled.
|
||||
assert!("hive-c0re".parse::<InfraContainer>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infra_container_name_round_trips() {
|
||||
// `unit_name` is the single source of truth for the wire form (the
|
||||
// serde impls + FromStr all key off it), so a name→variant→name
|
||||
// round-trip proves the mapping is consistent in both directions.
|
||||
for c in InfraContainer::ALL {
|
||||
assert_eq!(c.unit_name().parse::<InfraContainer>(), Ok(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue