Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01c0a6dcba | ||
|
|
fa139342ed | ||
|
|
7773f10646 | ||
|
|
46456f75ce | ||
|
|
299add158f | ||
|
|
0d6c003fa8 |
34 changed files with 135 additions and 243 deletions
27
Cargo.lock
generated
27
Cargo.lock
generated
|
|
@ -1767,6 +1767,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2 0.11.0",
|
"sha2 0.11.0",
|
||||||
|
"strum",
|
||||||
"swarm-queue-client",
|
"swarm-queue-client",
|
||||||
"swarm-secret-client",
|
"swarm-secret-client",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
|
@ -1814,6 +1815,7 @@ dependencies = [
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"strum",
|
||||||
"time",
|
"time",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
@ -1965,6 +1967,7 @@ dependencies = [
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"strum",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4662,6 +4665,27 @@ version = "0.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.28.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
|
||||||
|
dependencies = [
|
||||||
|
"strum_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.28.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.6.1"
|
version = "2.6.1"
|
||||||
|
|
@ -4721,6 +4745,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2 0.11.0",
|
"sha2 0.11.0",
|
||||||
|
"strum",
|
||||||
"swarm-authelia-bridge-sock",
|
"swarm-authelia-bridge-sock",
|
||||||
"swarm-queue-client",
|
"swarm-queue-client",
|
||||||
"swarm-secret-client",
|
"swarm-secret-client",
|
||||||
|
|
@ -4761,6 +4786,7 @@ dependencies = [
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"strum",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
@ -4775,6 +4801,7 @@ dependencies = [
|
||||||
"rustify_derive",
|
"rustify_derive",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"strum",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"vaultrs",
|
"vaultrs",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,10 @@ schemars = "1.0"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
similar = "2"
|
similar = "2"
|
||||||
|
# `derive`-only: `Kind`'s segment strings via `#[strum(serialize = "...")]`
|
||||||
|
# instead of a hand-written match, so the derive macro is the single source
|
||||||
|
# of truth (see `swarm-secret-client::path::Kind`).
|
||||||
|
strum = { version = "0.28.0", features = ["derive"] }
|
||||||
tokio = { version = "1", features = [
|
tokio = { version = "1", features = [
|
||||||
"fs",
|
"fs",
|
||||||
"io-util",
|
"io-util",
|
||||||
|
|
|
||||||
|
|
@ -848,7 +848,7 @@ progresses (a rebuild reads `stop_for_update`, then `swap`, then
|
||||||
life). Two consequences for anything rendering it:
|
life). Two consequences for anything rendering it:
|
||||||
|
|
||||||
- The label vocabulary is **open** — it's the node's own wire tag
|
- The label vocabulary is **open** — it's the node's own wire tag
|
||||||
(`NodeKind::as_str`, the same strings the graph wire's node
|
(`NodeKind`'s `&str` conversion, the same strings the graph wire's node
|
||||||
labels carry), not a fixed set. Treat it as an opaque display
|
labels carry), not a fixed set. Treat it as an opaque display
|
||||||
string; don't switch on specific values. `restarting` in
|
string; don't switch on specific values. `restarting` in
|
||||||
particular no longer exists, because no node kind is unique to a
|
particular no longer exists, because no node kind is unique to a
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ sha2.workspace = true
|
||||||
rusqlite.workspace = true
|
rusqlite.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
strum.workspace = true
|
||||||
# Offering this hive's status to the swarm (`swarm_status`). The same crate
|
# Offering this hive's status to the swarm (`swarm_status`). The same crate
|
||||||
# the swarm controller reads it with, and `kv` for the same reason: the
|
# the swarm controller reads it with, and `kv` for the same reason: the
|
||||||
# bucket's name and creation config belong to neither end of it alone.
|
# bucket's name and creation config belong to neither end of it alone.
|
||||||
|
|
|
||||||
|
|
@ -712,7 +712,7 @@ async fn finish_approval(
|
||||||
// snapshot refetch. `approved` rows that succeed get the
|
// snapshot refetch. `approved` rows that succeed get the
|
||||||
// approval's logged resolved_at indirectly via `Utc::now()`;
|
// approval's logged resolved_at indirectly via `Utc::now()`;
|
||||||
// failures already wrote it via mark_failed above.
|
// failures already wrote it via mark_failed above.
|
||||||
let approval_kind = approval.kind.as_str();
|
let approval_kind = <&str>::from(approval.kind);
|
||||||
let sha_short = approval
|
let sha_short = approval
|
||||||
.fetched_sha
|
.fetched_sha
|
||||||
.as_deref()
|
.as_deref()
|
||||||
|
|
@ -921,7 +921,7 @@ pub fn deny(coord: &Coordinator, id: i64, note: Option<&str>) -> Result<()> {
|
||||||
tracing::info!(%id, note, "approval denied");
|
tracing::info!(%id, note, "approval denied");
|
||||||
if let Some(a) = approval {
|
if let Some(a) = approval {
|
||||||
let sha = a.fetched_sha.clone();
|
let sha = a.fetched_sha.clone();
|
||||||
let approval_kind = a.kind.as_str();
|
let approval_kind = <&str>::from(a.kind);
|
||||||
let sha_short = sha.as_deref().map(|s| s[..s.len().min(12)].to_owned());
|
let sha_short = sha.as_deref().map(|s| s[..s.len().min(12)].to_owned());
|
||||||
let description = a.description.clone();
|
let description = a.description.clone();
|
||||||
let agent_owned = a.agent.clone();
|
let agent_owned = a.agent.clone();
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ pub fn caps_for(name: &str) -> Vec<String> {
|
||||||
pub fn has_cap(name: &str, cap: hive_sh4re::permissions::Capability) -> bool {
|
pub fn has_cap(name: &str, cap: hive_sh4re::permissions::Capability) -> bool {
|
||||||
caps_for(name)
|
caps_for(name)
|
||||||
.iter()
|
.iter()
|
||||||
.any(|s| s.eq_ignore_ascii_case(cap.as_str()))
|
.any(|s| s.eq_ignore_ascii_case(<&str>::from(cap)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Persist the full capability map. Sorted JSON output keeps diffs
|
/// Persist the full capability map. Sorted JSON output keeps diffs
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ fn write(map: &BTreeMap<String, Vec<String>>) -> std::io::Result<()> {
|
||||||
pub fn validate_groups(groups: &[String]) -> anyhow::Result<()> {
|
pub fn validate_groups(groups: &[String]) -> anyhow::Result<()> {
|
||||||
let valid: std::collections::BTreeSet<&str> = hive_sh4re::permissions::ToolGroup::ALL
|
let valid: std::collections::BTreeSet<&str> = hive_sh4re::permissions::ToolGroup::ALL
|
||||||
.iter()
|
.iter()
|
||||||
.map(|g| g.as_str())
|
.map(|g| <&str>::from(*g))
|
||||||
.collect();
|
.collect();
|
||||||
let unknown: Vec<&str> = groups
|
let unknown: Vec<&str> = groups
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -87,7 +87,7 @@ pub fn validate_groups(groups: &[String]) -> anyhow::Result<()> {
|
||||||
unknown.join(", "),
|
unknown.join(", "),
|
||||||
hive_sh4re::permissions::ToolGroup::ALL
|
hive_sh4re::permissions::ToolGroup::ALL
|
||||||
.iter()
|
.iter()
|
||||||
.map(|g| g.as_str())
|
.map(|g| <&str>::from(*g))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(", ")
|
.join(", ")
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -638,10 +638,10 @@ impl Coordinator {
|
||||||
/// commits the JSON file, so the P3RM1SS10NS tab updates live.
|
/// commits the JSON file, so the P3RM1SS10NS tab updates live.
|
||||||
pub fn emit_capabilities_snapshot(self: &Arc<Self>) {
|
pub fn emit_capabilities_snapshot(self: &Arc<Self>) {
|
||||||
use hive_sh4re::permissions::Capability;
|
use hive_sh4re::permissions::Capability;
|
||||||
let caps = Capability::ALL.iter().map(|c| c.as_str()).collect();
|
let caps = Capability::ALL.iter().map(|c| <&str>::from(*c)).collect();
|
||||||
let descriptions = Capability::ALL
|
let descriptions = Capability::ALL
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| (c.as_str(), c.description()))
|
.map(|c| (<&str>::from(*c), c.description()))
|
||||||
.collect();
|
.collect();
|
||||||
let assignments = crate::capabilities::read();
|
let assignments = crate::capabilities::read();
|
||||||
// Best-effort roster (sync path); on a contended cache miss we
|
// Best-effort roster (sync path); on a contended cache miss we
|
||||||
|
|
@ -665,10 +665,10 @@ impl Coordinator {
|
||||||
/// commits the JSON file, so the P3RM1SS10NS tab updates live.
|
/// commits the JSON file, so the P3RM1SS10NS tab updates live.
|
||||||
pub fn emit_tool_groups_snapshot(self: &Arc<Self>) {
|
pub fn emit_tool_groups_snapshot(self: &Arc<Self>) {
|
||||||
use hive_sh4re::permissions::ToolGroup;
|
use hive_sh4re::permissions::ToolGroup;
|
||||||
let groups = ToolGroup::ALL.iter().map(|g| g.as_str()).collect();
|
let groups = ToolGroup::ALL.iter().map(|g| <&str>::from(*g)).collect();
|
||||||
let descriptions = ToolGroup::ALL
|
let descriptions = ToolGroup::ALL
|
||||||
.iter()
|
.iter()
|
||||||
.map(|g| (g.as_str(), g.description()))
|
.map(|g| (<&str>::from(*g), g.description()))
|
||||||
.collect();
|
.collect();
|
||||||
let assignments = crate::tool_groups::read();
|
let assignments = crate::tool_groups::read();
|
||||||
let roster = self.live_container_names_blocking().unwrap_or_default();
|
let roster = self.live_container_names_blocking().unwrap_or_default();
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ pub(super) fn gc_orphans(coord: &Coordinator, approvals: Vec<Approval>) -> Vec<A
|
||||||
coord.emit_approval_resolved(crate::coordinator::ApprovalResolved {
|
coord.emit_approval_resolved(crate::coordinator::ApprovalResolved {
|
||||||
id: a.id,
|
id: a.id,
|
||||||
agent: a.agent.as_str(),
|
agent: a.agent.as_str(),
|
||||||
approval_kind: a.kind.as_str(),
|
approval_kind: <&str>::from(a.kind),
|
||||||
sha_short,
|
sha_short,
|
||||||
status: "failed",
|
status: "failed",
|
||||||
note: Some(note.to_owned()),
|
note: Some(note.to_owned()),
|
||||||
|
|
|
||||||
|
|
@ -50,11 +50,11 @@ pub(super) async fn get_tool_groups(
|
||||||
) -> axum::Json<ToolGroupsSnapshot> {
|
) -> axum::Json<ToolGroupsSnapshot> {
|
||||||
let groups = hive_sh4re::permissions::ToolGroup::ALL
|
let groups = hive_sh4re::permissions::ToolGroup::ALL
|
||||||
.iter()
|
.iter()
|
||||||
.map(|g| g.as_str())
|
.map(|g| <&str>::from(*g))
|
||||||
.collect();
|
.collect();
|
||||||
let descriptions = hive_sh4re::permissions::ToolGroup::ALL
|
let descriptions = hive_sh4re::permissions::ToolGroup::ALL
|
||||||
.iter()
|
.iter()
|
||||||
.map(|g| (g.as_str(), g.description()))
|
.map(|g| (<&str>::from(*g), g.description()))
|
||||||
.collect();
|
.collect();
|
||||||
let assignments = crate::tool_groups::read();
|
let assignments = crate::tool_groups::read();
|
||||||
let roster = state
|
let roster = state
|
||||||
|
|
@ -82,7 +82,7 @@ pub(super) async fn get_tool_groups(
|
||||||
pub(crate) fn tool_group_default_names() -> Vec<&'static str> {
|
pub(crate) fn tool_group_default_names() -> Vec<&'static str> {
|
||||||
hive_sh4re::permissions::ToolGroup::AGENT_DEFAULT
|
hive_sh4re::permissions::ToolGroup::AGENT_DEFAULT
|
||||||
.iter()
|
.iter()
|
||||||
.map(|g| g.as_str())
|
.map(|g| <&str>::from(*g))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,10 +205,10 @@ pub(super) async fn get_capabilities(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
) -> axum::Json<CapabilitiesSnapshot> {
|
) -> axum::Json<CapabilitiesSnapshot> {
|
||||||
use hive_sh4re::permissions::Capability;
|
use hive_sh4re::permissions::Capability;
|
||||||
let caps = Capability::ALL.iter().map(|c| c.as_str()).collect();
|
let caps = Capability::ALL.iter().map(|c| <&str>::from(*c)).collect();
|
||||||
let descriptions = Capability::ALL
|
let descriptions = Capability::ALL
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| (c.as_str(), c.description()))
|
.map(|c| (<&str>::from(*c), c.description()))
|
||||||
.collect();
|
.collect();
|
||||||
let assignments = crate::capabilities::read();
|
let assignments = crate::capabilities::read();
|
||||||
let roster = state
|
let roster = state
|
||||||
|
|
@ -258,7 +258,7 @@ pub(super) async fn post_capabilities(
|
||||||
}
|
}
|
||||||
let known: Vec<&str> = hive_sh4re::permissions::Capability::ALL
|
let known: Vec<&str> = hive_sh4re::permissions::Capability::ALL
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| c.as_str())
|
.map(|c| <&str>::from(*c))
|
||||||
.collect();
|
.collect();
|
||||||
for cap in &body.caps {
|
for cap in &body.caps {
|
||||||
if !known.contains(&cap.as_str()) {
|
if !known.contains(&cap.as_str()) {
|
||||||
|
|
@ -336,7 +336,7 @@ pub(super) async fn post_permissions(
|
||||||
) -> Result<Response, ProblemDetails> {
|
) -> Result<Response, ProblemDetails> {
|
||||||
let known_caps: Vec<&str> = hive_sh4re::permissions::Capability::ALL
|
let known_caps: Vec<&str> = hive_sh4re::permissions::Capability::ALL
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| c.as_str())
|
.map(|c| <&str>::from(*c))
|
||||||
.collect();
|
.collect();
|
||||||
// Phase 1 — validate everything before touching any file or the
|
// Phase 1 — validate everything before touching any file or the
|
||||||
// queue, so a bad entry fails the whole POST with zero side effects.
|
// queue, so a bad entry fails the whole POST with zero side effects.
|
||||||
|
|
|
||||||
|
|
@ -423,7 +423,7 @@ fn history_view(a: Approval) -> ApprovalHistoryView {
|
||||||
// Pending shouldn't appear in recent_resolved, but be defensive.
|
// Pending shouldn't appear in recent_resolved, but be defensive.
|
||||||
hive_sh4re::approvals::ApprovalStatus::Pending => "pending",
|
hive_sh4re::approvals::ApprovalStatus::Pending => "pending",
|
||||||
};
|
};
|
||||||
let kind = a.kind.as_str();
|
let kind = <&str>::from(a.kind);
|
||||||
ApprovalHistoryView {
|
ApprovalHistoryView {
|
||||||
id: a.id,
|
id: a.id,
|
||||||
agent: a.agent.to_string(),
|
agent: a.agent.to_string(),
|
||||||
|
|
|
||||||
|
|
@ -385,7 +385,7 @@ fn run_set_wanted(coord: &Arc<Coordinator>, agent: &str, up: bool) -> Result<()>
|
||||||
coord
|
coord
|
||||||
.power
|
.power
|
||||||
.set(agent, wanted)
|
.set(agent, wanted)
|
||||||
.with_context(|| format!("set wanted={} for agent {agent}", wanted.as_str()))?;
|
.with_context(|| format!("set wanted={} for agent {agent}", <&str>::from(wanted)))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -579,7 +579,7 @@ async fn run_reconcile(coord: &Arc<Coordinator>, name: &str) -> Result<Option<No
|
||||||
agent: name.to_owned(),
|
agent: name.to_owned(),
|
||||||
}),
|
}),
|
||||||
ReconcileAction::Noop => {
|
ReconcileAction::Noop => {
|
||||||
tracing::debug!(%name, wanted = wanted.as_str(), running, "reconcile: noop");
|
tracing::debug!(%name, wanted = <&str>::from(wanted), running, "reconcile: noop");
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -281,7 +281,7 @@ impl JobQueue {
|
||||||
/// so both are running and both name `a`. Anything keying this set by agent
|
/// so both are running and both name `a`. Anything keying this set by agent
|
||||||
/// alone will silently drop one — see [`scheduler`].
|
/// alone will silently drop one — see [`scheduler`].
|
||||||
///
|
///
|
||||||
/// `label` is the node's own wire tag ([`NodeKind::as_str`]), the same
|
/// `label` is the node's own wire tag (`NodeKind`'s `&str` conversion), the same
|
||||||
/// vocabulary the graph wire ships as a node's label, so a pill and a
|
/// vocabulary the graph wire ships as a node's label, so a pill and a
|
||||||
/// graph node name an operation identically. `takes_container_down` is the
|
/// graph node name an operation identically. `takes_container_down` is the
|
||||||
/// crash watcher's
|
/// crash watcher's
|
||||||
|
|
@ -313,7 +313,7 @@ impl JobQueue {
|
||||||
}
|
}
|
||||||
Some(RunningTransient {
|
Some(RunningTransient {
|
||||||
agent: agent.to_owned(),
|
agent: agent.to_owned(),
|
||||||
label: n.payload.as_str().to_owned(),
|
label: <&str>::from(&n.payload).to_owned(),
|
||||||
takes_container_down: n.payload.takes_container_down(),
|
takes_container_down: n.payload.takes_container_down(),
|
||||||
// `started_at` is set when a node enters `Running`, and this
|
// `started_at` is set when a node enters `Running`, and this
|
||||||
// only sees `Running` nodes — the fallback is unreachable in
|
// only sees `Running` nodes — the fallback is unreachable in
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,9 @@ use hive_jobq::TerminalState;
|
||||||
/// compose into live in the node-inventory table and surrounding sections
|
/// compose into live in the node-inventory table and surrounding sections
|
||||||
/// of `docs/scheduler/coordinator.md` — this enum is deliberately not a
|
/// of `docs/scheduler/coordinator.md` — this enum is deliberately not a
|
||||||
/// second copy of that; each variant below gets a one-line pointer.
|
/// second copy of that; each variant below gets a one-line pointer.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, strum::IntoStaticStr)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum NodeKind {
|
pub enum NodeKind {
|
||||||
/// The rebuild's meta-repo preamble. `relock = false` only for
|
/// The rebuild's meta-repo preamble. `relock = false` only for
|
||||||
/// meta-update cascade rebuilds (re-locking would revert the bump the
|
/// meta-update cascade rebuilds (re-locking would revert the bump the
|
||||||
|
|
@ -169,7 +170,7 @@ pub enum NodeKind {
|
||||||
/// renders it without knowing what any of it means.
|
/// renders it without knowing what any of it means.
|
||||||
impl hive_jobq_wire::WireNode for NodeKind {
|
impl hive_jobq_wire::WireNode for NodeKind {
|
||||||
fn label(&self) -> String {
|
fn label(&self) -> String {
|
||||||
self.as_str().to_owned()
|
<&str>::from(self).to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self, id: hive_jobq_wire::WireId) -> serde_json::Value {
|
fn data(&self, id: hive_jobq_wire::WireId) -> serde_json::Value {
|
||||||
|
|
@ -200,48 +201,6 @@ impl hive_jobq_wire::WireNode for NodeKind {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NodeKind {
|
impl NodeKind {
|
||||||
/// Wire string for the node's label on the graph wire
|
|
||||||
/// ([`hive_jobq_wire::WireNode::label`]).
|
|
||||||
pub fn as_str(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
NodeKind::MetaSync { .. } => "meta_sync",
|
|
||||||
NodeKind::Prebuild { .. } => "prebuild",
|
|
||||||
NodeKind::Swap { .. } => "swap",
|
|
||||||
NodeKind::RebuildBookkeeping { .. } => "rebuild_bookkeeping",
|
|
||||||
NodeKind::Provision { .. } => "provision",
|
|
||||||
NodeKind::Create { .. } => "create",
|
|
||||||
NodeKind::DestroyContainer { .. } => "destroy_container",
|
|
||||||
NodeKind::PurgeState { .. } => "purge_state",
|
|
||||||
NodeKind::DestroyBookkeeping { .. } => "destroy_bookkeeping",
|
|
||||||
NodeKind::MetaLock { .. } => "meta_lock",
|
|
||||||
NodeKind::Reconcile { .. } => "reconcile",
|
|
||||||
NodeKind::Start { .. } => "start",
|
|
||||||
NodeKind::Stop { .. } => "stop",
|
|
||||||
NodeKind::StopForUpdate { .. } => "stop_for_update",
|
|
||||||
NodeKind::Signal { .. } => "signal",
|
|
||||||
NodeKind::Drain { .. } => "drain",
|
|
||||||
NodeKind::PauseSignal { .. } => "pause_signal",
|
|
||||||
NodeKind::PauseDrain { .. } => "pause_drain",
|
|
||||||
NodeKind::WriteDropin { .. } => "write_dropin",
|
|
||||||
NodeKind::WritePermFile { .. } => "write_perm_file",
|
|
||||||
NodeKind::Reparent { .. } => "reparent",
|
|
||||||
NodeKind::DeployWindow { .. } => "deploy_window",
|
|
||||||
NodeKind::AgentWindow { .. } => "agent_window",
|
|
||||||
NodeKind::MergeVerify { .. } => "merge_verify",
|
|
||||||
NodeKind::DeployApply { .. } => "deploy_apply",
|
|
||||||
NodeKind::FinalizeDeploy { .. } => "finalize_deploy",
|
|
||||||
NodeKind::DeployTail { .. } => "deploy_tail",
|
|
||||||
NodeKind::ResolveApproval { .. } => "resolve_approval",
|
|
||||||
NodeKind::EmitRebuilt { .. } => "emit_rebuilt",
|
|
||||||
NodeKind::SetWanted { .. } => "set_wanted",
|
|
||||||
NodeKind::ForgeSweep => "forge_sweep",
|
|
||||||
NodeKind::MatrixSweep => "matrix_sweep",
|
|
||||||
NodeKind::WebhookRegister => "webhook_register",
|
|
||||||
NodeKind::KnowledgePull => "knowledge_pull",
|
|
||||||
NodeKind::WantedPull => "wanted_pull",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The agent this node targets, or `""` for agentless kinds
|
/// The agent this node targets, or `""` for agentless kinds
|
||||||
/// ([`NodeKind::MetaLock`] on the `hyperhive` pseudo-agent,
|
/// ([`NodeKind::MetaLock`] on the `hyperhive` pseudo-agent,
|
||||||
/// [`NodeKind::Reparent`] which can span multiple agents, and
|
/// [`NodeKind::Reparent`] which can span multiple agents, and
|
||||||
|
|
|
||||||
|
|
@ -92,10 +92,11 @@ pub async fn run_worker(coord: Arc<Coordinator>) {
|
||||||
hive_jobq::scheduler::Scheduler::claim_next(&sched, move |id, kind, builder| {
|
hive_jobq::scheduler::Scheduler::claim_next(&sched, move |id, kind, builder| {
|
||||||
let coord = node_coord;
|
let coord = node_coord;
|
||||||
async move {
|
async move {
|
||||||
|
let kind_str = <&str>::from(&kind);
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
dag = coord.job_queue.root_of(id).unwrap_or_default(),
|
dag = coord.job_queue.root_of(id).unwrap_or_default(),
|
||||||
node = id.get(),
|
node = id.get(),
|
||||||
kind = kind.as_str(),
|
kind = kind_str,
|
||||||
agent = %kind.agent(),
|
agent = %kind.agent(),
|
||||||
"job_queue: node running"
|
"job_queue: node running"
|
||||||
);
|
);
|
||||||
|
|
@ -104,7 +105,7 @@ pub async fn run_worker(coord: Arc<Coordinator>) {
|
||||||
Ok(()) => tracing::info!(node = id.get(), "job_queue: node done"),
|
Ok(()) => tracing::info!(node = id.get(), "job_queue: node done"),
|
||||||
Err(e) => tracing::warn!(
|
Err(e) => tracing::warn!(
|
||||||
node = id.get(),
|
node = id.get(),
|
||||||
kind = kind.as_str(),
|
kind = kind_str,
|
||||||
agent = %kind.agent(),
|
agent = %kind.agent(),
|
||||||
error = %format!("{e:#}"),
|
error = %format!("{e:#}"),
|
||||||
grown_nodes = !grown.is_empty(),
|
grown_nodes = !grown.is_empty(),
|
||||||
|
|
|
||||||
|
|
@ -146,12 +146,12 @@ fn declared_shape(q: &JobQueue) -> Vec<Declared> {
|
||||||
fn declared_shape_filtered(q: &JobQueue, keep: &dyn Fn(&NodeKind) -> bool) -> Vec<Declared> {
|
fn declared_shape_filtered(q: &JobQueue, keep: &dyn Fn(&NodeKind) -> bool) -> Vec<Declared> {
|
||||||
let sched = q.sched().lock().expect("job_queue mutex poisoned");
|
let sched = q.sched().lock().expect("job_queue mutex poisoned");
|
||||||
let graph = sched.graph();
|
let graph = sched.graph();
|
||||||
let kind_of = |id: NodeId| graph.node(id).map(|n| n.payload.as_str());
|
let kind_of = |id: NodeId| graph.node(id).map(|n| <&str>::from(&n.payload));
|
||||||
graph
|
graph
|
||||||
.nodes()
|
.nodes()
|
||||||
.filter(|n| keep(&n.payload))
|
.filter(|n| keep(&n.payload))
|
||||||
.map(|n| Declared {
|
.map(|n| Declared {
|
||||||
kind: n.payload.as_str(),
|
kind: <&str>::from(&n.payload),
|
||||||
parent: n.parent.and_then(kind_of),
|
parent: n.parent.and_then(kind_of),
|
||||||
after: n
|
after: n
|
||||||
.deps
|
.deps
|
||||||
|
|
@ -175,7 +175,7 @@ fn node_of(q: &JobQueue, kind: &str) -> hive_jobq::NodeId {
|
||||||
let graph = sched.graph();
|
let graph = sched.graph();
|
||||||
let mut found: Vec<_> = graph
|
let mut found: Vec<_> = graph
|
||||||
.nodes()
|
.nodes()
|
||||||
.filter(|n| n.payload.as_str() == kind)
|
.filter(|n| <&str>::from(&n.payload) == kind)
|
||||||
.map(|n| n.id)
|
.map(|n| n.id)
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -225,7 +225,7 @@ fn pending_kinds_filtered(q: &JobQueue, keep: &dyn Fn(&NodeKind) -> bool) -> Vec
|
||||||
graph
|
graph
|
||||||
.nodes()
|
.nodes()
|
||||||
.filter(|n| n.state == State::Pending && keep(&n.payload))
|
.filter(|n| n.state == State::Pending && keep(&n.payload))
|
||||||
.map(|n| n.payload.as_str())
|
.map(|n| <&str>::from(&n.payload))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -275,7 +275,7 @@ fn declared_resources_of_kind(q: &JobQueue, kind: &str) -> Vec<Vec<Resource>> {
|
||||||
let graph = sched.graph();
|
let graph = sched.graph();
|
||||||
let mut rows: Vec<Vec<Resource>> = graph
|
let mut rows: Vec<Vec<Resource>> = graph
|
||||||
.nodes()
|
.nodes()
|
||||||
.filter(|n| n.payload.as_str() == kind)
|
.filter(|n| <&str>::from(&n.payload) == kind)
|
||||||
.map(|n| {
|
.map(|n| {
|
||||||
n.deps
|
n.deps
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -862,7 +862,7 @@ fn rebuild_chain_nodes_suppress_crash_watch() {
|
||||||
kind.takes_container_down(),
|
kind.takes_container_down(),
|
||||||
"{} must suppress crash-watch — a rebuild takes the container down \
|
"{} must suppress crash-watch — a rebuild takes the container down \
|
||||||
on purpose",
|
on purpose",
|
||||||
kind.as_str()
|
<&str>::from(&kind)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// The counter-case, and the reason this can't be "any node in a rebuild":
|
// The counter-case, and the reason this can't be "any node in a rebuild":
|
||||||
|
|
|
||||||
|
|
@ -823,7 +823,7 @@ fn handle_cancel_loose_end(
|
||||||
coord.emit_approval_resolved(crate::coordinator::ApprovalResolved {
|
coord.emit_approval_resolved(crate::coordinator::ApprovalResolved {
|
||||||
id: approval.id,
|
id: approval.id,
|
||||||
agent: approval.agent.as_str(),
|
agent: approval.agent.as_str(),
|
||||||
approval_kind: approval.kind.as_str(),
|
approval_kind: <&str>::from(approval.kind),
|
||||||
sha_short,
|
sha_short,
|
||||||
status: "cancelled",
|
status: "cancelled",
|
||||||
note: approval.note,
|
note: approval.note,
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ impl Approvals {
|
||||||
VALUES (?1, ?2, ?3, ?4, 'pending', ?5, ?6, ?7)",
|
VALUES (?1, ?2, ?3, ?4, 'pending', ?5, ?6, ?7)",
|
||||||
params![
|
params![
|
||||||
agent,
|
agent,
|
||||||
kind.as_str(),
|
<&str>::from(kind),
|
||||||
commit_ref,
|
commit_ref,
|
||||||
Utc::now().timestamp(),
|
Utc::now().timestamp(),
|
||||||
description,
|
description,
|
||||||
|
|
|
||||||
|
|
@ -90,8 +90,9 @@ const MIGRATIONS: &[Migration] = &[Migration {
|
||||||
|
|
||||||
/// Status of a finished build attempt. Stored as the literal string in
|
/// Status of a finished build attempt. Stored as the literal string in
|
||||||
/// the `status` column; `NULL` while the attempt is still in progress.
|
/// the `status` column; `NULL` while the attempt is still in progress.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, strum::IntoStaticStr)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum BuildStatus {
|
pub enum BuildStatus {
|
||||||
/// Child exited with success.
|
/// Child exited with success.
|
||||||
Ok,
|
Ok,
|
||||||
|
|
@ -99,15 +100,6 @@ pub enum BuildStatus {
|
||||||
Fail,
|
Fail,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuildStatus {
|
|
||||||
fn as_str(self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Self::Ok => "ok",
|
|
||||||
Self::Fail => "fail",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Header-only row returned by `list_recent_for_agent`. Carries the
|
/// Header-only row returned by `list_recent_for_agent`. Carries the
|
||||||
/// metadata the dashboard's agent-card chip needs (status + age +
|
/// metadata the dashboard's agent-card chip needs (status + age +
|
||||||
/// id-to-open) without the multi-MB stdout/stderr payload.
|
/// id-to-open) without the multi-MB stdout/stderr payload.
|
||||||
|
|
@ -280,7 +272,7 @@ impl BuildLogs {
|
||||||
let conn = self.conn.lock().unwrap();
|
let conn = self.conn.lock().unwrap();
|
||||||
if let Err(e) = conn.execute(
|
if let Err(e) = conn.execute(
|
||||||
"UPDATE build_logs SET finished_at = ?1, status = ?2 WHERE id = ?3",
|
"UPDATE build_logs SET finished_at = ?1, status = ?2 WHERE id = ?3",
|
||||||
params![now, status.as_str(), id],
|
params![now, <&str>::from(status), id],
|
||||||
) {
|
) {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
build_log_id = id,
|
build_log_id = id,
|
||||||
|
|
|
||||||
|
|
@ -30,28 +30,14 @@ CREATE TABLE IF NOT EXISTS agent_power (
|
||||||
";
|
";
|
||||||
|
|
||||||
/// Per-agent power intent.
|
/// Per-agent power intent.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::IntoStaticStr, strum::EnumString)]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum Wanted {
|
pub enum Wanted {
|
||||||
Up,
|
Up,
|
||||||
Offline,
|
Offline,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Wanted {
|
impl Wanted {
|
||||||
pub fn as_str(self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Wanted::Up => "up",
|
|
||||||
Wanted::Offline => "offline",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse(s: &str) -> Option<Self> {
|
|
||||||
match s {
|
|
||||||
"up" => Some(Wanted::Up),
|
|
||||||
"offline" => Some(Wanted::Offline),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Seed value from an observed running state (first boot after
|
/// Seed value from an observed running state (first boot after
|
||||||
/// this store lands, or an agent spawned outside the normal path).
|
/// this store lands, or an agent spawned outside the normal path).
|
||||||
pub fn from_running(running: bool) -> Self {
|
pub fn from_running(running: bool) -> Self {
|
||||||
|
|
@ -128,7 +114,7 @@ impl PowerStore {
|
||||||
.context("select agent_power")?;
|
.context("select agent_power")?;
|
||||||
match row {
|
match row {
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
Some(stored) => Wanted::parse(&stored).map(Some).with_context(|| {
|
Some(stored) => stored.parse::<Wanted>().ok().map(Some).with_context(|| {
|
||||||
format!("agent_power row for {agent} holds an unknown intent {stored:?}")
|
format!("agent_power row for {agent} holds an unknown intent {stored:?}")
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
@ -141,7 +127,7 @@ impl PowerStore {
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO agent_power (agent, wanted, updated_at) VALUES (?1, ?2, ?3)
|
"INSERT INTO agent_power (agent, wanted, updated_at) VALUES (?1, ?2, ?3)
|
||||||
ON CONFLICT(agent) DO UPDATE SET wanted = ?2, updated_at = ?3",
|
ON CONFLICT(agent) DO UPDATE SET wanted = ?2, updated_at = ?3",
|
||||||
params![agent, wanted.as_str(), Utc::now().timestamp()],
|
params![agent, <&str>::from(wanted), Utc::now().timestamp()],
|
||||||
)
|
)
|
||||||
.context("upsert agent_power")?;
|
.context("upsert agent_power")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -157,7 +143,7 @@ impl PowerStore {
|
||||||
}
|
}
|
||||||
let seeded = Wanted::from_running(running);
|
let seeded = Wanted::from_running(running);
|
||||||
self.set(agent, seeded)?;
|
self.set(agent, seeded)?;
|
||||||
tracing::info!(%agent, wanted = seeded.as_str(), "agent_power: seeded from observed state");
|
tracing::info!(%agent, wanted = <&str>::from(seeded), "agent_power: seeded from observed state");
|
||||||
Ok(seeded)
|
Ok(seeded)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue