Make agent creation swarm-only and refuse a name placed on another hive
swarm-controller's POST /api/agents now refuses (409) a name the swarm
has already placed on a different hive: a non-Destroyed declaration in
that hive's wanted state, or a SetAgentWanted node still queued for it.
The same name on the same hive is that agent being re-created and goes
through. A wanted state that cannot be read refuses (503/500) instead of
reading as "placed nowhere". Creations are serialised from that read to
the graph insert so two concurrent creations of one name cannot both
pass.
Hive-level creation is removed: hivectl `agent create` / `request-create`,
HostRequest::Spawn / RequestSpawn, the dashboard POST /api/request-spawn
route, and ApprovalKind::Spawn with its approve/resolve arms and the
approval-carrying `templates::spawn`. The swarm path (deploy request or
wanted-state sweep -> queue_first_deploy -> templates::first_deploy) used
none of them. Old `spawn` approval rows are skipped by collect_lenient,
as `init_config` rows were in a3b672d1.
policy.rs's comment on agent_object_name stated swarm-wide name
uniqueness as a fact; it now says where it is enforced and what that
check cannot see.
Refs #4396
This commit is contained in:
parent
1d8ec00ddc
commit
5785c0024c
35 changed files with 376 additions and 434 deletions
|
|
@ -54,7 +54,7 @@ async fn agents_restart(socket: &Path, name: &str, no_wait: bool) -> Result<()>
|
|||
async fn agents_start(socket: &Path, name: &str, paused: bool) -> Result<()> {
|
||||
if !crate::util::agent_exists(socket, name).await? {
|
||||
bail!(
|
||||
"no such agent: '{name}' (no state dir under {}/) — use 'hivectl agent {name} create' to provision a brand-new agent",
|
||||
"no such agent: '{name}' (no state dir under {}/) — a brand-new agent is created at swarm level ('swarmctl agent create')",
|
||||
hive_host_sock::AGENTS_ROOT
|
||||
);
|
||||
}
|
||||
|
|
@ -269,14 +269,6 @@ pub(crate) async fn run_agent(socket: &Path, name: &str, cmd: AgentCmd) -> Resul
|
|||
AgentCmd::Pause { no_wait } => set_paused(socket, name, true, no_wait).await,
|
||||
AgentCmd::Resume => set_paused(socket, name, false, false).await,
|
||||
AgentCmd::Start { paused } => agents_start(socket, name, paused).await,
|
||||
AgentCmd::Create => {
|
||||
let name = crate::util::parse_ident(name)?;
|
||||
render(crate::client::request(socket, HostRequest::Spawn { name }).await?)
|
||||
}
|
||||
AgentCmd::RequestCreate => {
|
||||
let name = crate::util::parse_ident(name)?;
|
||||
render(crate::client::request(socket, HostRequest::RequestSpawn { name }).await?)
|
||||
}
|
||||
AgentCmd::Stop => agents_stop(socket, name).await,
|
||||
AgentCmd::Kill => {
|
||||
let name = crate::util::parse_ident(name)?;
|
||||
|
|
|
|||
|
|
@ -414,7 +414,7 @@ pub enum AgentCmd {
|
|||
Resume,
|
||||
/// Start this EXISTING agent container. Fails immediately if `name`
|
||||
/// has no config/topology entry at all — it never attempts
|
||||
/// first-time creation. Use `create` for that.
|
||||
/// first-time creation, which is swarm-level (`swarmctl agent create`).
|
||||
Start {
|
||||
/// Start (or leave) the agent paused: if it's currently down, hivectl
|
||||
/// writes the pause marker before the container boots, so it comes
|
||||
|
|
@ -424,14 +424,6 @@ pub enum AgentCmd {
|
|||
#[arg(long)]
|
||||
paused: bool,
|
||||
},
|
||||
/// Create this agent container from scratch (full first-time
|
||||
/// provisioning), bypassing the approval queue.
|
||||
///
|
||||
/// Operator-on-the-host only; use `request-create` for an
|
||||
/// approval-gated creation.
|
||||
Create,
|
||||
/// Queue a first-creation request for operator approval.
|
||||
RequestCreate,
|
||||
/// Gracefully stop this agent container: signal → drain → reconcile.
|
||||
/// Never escalates to a hard kill — use `kill` for that.
|
||||
Stop,
|
||||
|
|
|
|||
Loading…
Reference in a new issue