swarmctl: agent set-state declares an agent's wanted state
Wraps PUT /api/hives/{hive}/agents/{agent}/state, the route the swarm UI's
state menu uses. States come from swarm_wire::wanted::AgentState; destroyed
needs --yes.
Closes #6159
This commit is contained in:
parent
06b303aff2
commit
71d360c9d2
1 changed files with 187 additions and 1 deletions
|
|
@ -112,6 +112,51 @@ pub(crate) fn mint_forge_token(target: &Target, name: &str) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse a state as the wire enum spells it, so the accepted values are
|
||||
/// `swarm_wire::wanted::AgentState`'s and the error lists them.
|
||||
pub(crate) fn parse_state(value: &str) -> Result<swarm_wire::wanted::AgentState, String> {
|
||||
serde_json::from_value(serde_json::Value::String(value.to_owned())).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Refuse `destroyed` unless the operator confirmed it.
|
||||
fn check_confirmed(state: swarm_wire::wanted::AgentState, yes: bool) -> Result<()> {
|
||||
if state == swarm_wire::wanted::AgentState::Destroyed && !yes {
|
||||
bail!("`destroyed` can't be undone: pass --yes to declare it");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run `swarmctl agent set-state`: `PUT /api/hives/{hive}/agents/{agent}/state`.
|
||||
pub(crate) fn set_state(
|
||||
target: &Target,
|
||||
agent: &str,
|
||||
hive: &str,
|
||||
state: swarm_wire::wanted::AgentState,
|
||||
yes: bool,
|
||||
) -> Result<()> {
|
||||
check_confirmed(state, yes)?;
|
||||
let agent = parse_ident(agent, "agent name")?;
|
||||
let hive = parse_ident(hive, "hive")?;
|
||||
let wire: &'static str = state.into();
|
||||
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.context("starting a tokio runtime for the controller request")?;
|
||||
rt.block_on(send::<_, serde_json::Value>(
|
||||
target,
|
||||
hyper::Method::PUT,
|
||||
&format!("/api/hives/{hive}/agents/{agent}/state"),
|
||||
Some(&serde_json::json!({ "state": wire })),
|
||||
"set-state",
|
||||
))?;
|
||||
|
||||
println!(
|
||||
"declared agent {agent:?} {wire} on hive {hive:?}; the hive converges on its own clock"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// One `POST /api/agents` round trip at the controller.
|
||||
async fn post_create(
|
||||
target: &Target,
|
||||
|
|
@ -280,7 +325,33 @@ fn connect_hint(socket: &Path) -> String {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{describe_error, parse_ident};
|
||||
use super::{check_confirmed, describe_error, parse_ident, parse_state};
|
||||
use swarm_wire::wanted::AgentState;
|
||||
|
||||
/// The accepted values are the wire enum's: each spelling it writes
|
||||
/// parses back, and an unknown one names the valid ones.
|
||||
#[test]
|
||||
fn states_parse_through_the_wire_enum() {
|
||||
for state in [
|
||||
AgentState::Up,
|
||||
AgentState::Offline,
|
||||
AgentState::Paused,
|
||||
AgentState::Destroyed,
|
||||
] {
|
||||
let wire: &'static str = state.into();
|
||||
assert_eq!(parse_state(wire), Ok(state));
|
||||
}
|
||||
let err = parse_state("sleeping").expect_err("unknown state");
|
||||
assert!(err.contains("paused"), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn destroyed_needs_yes() {
|
||||
assert!(check_confirmed(AgentState::Destroyed, false).is_err());
|
||||
assert!(check_confirmed(AgentState::Destroyed, true).is_ok());
|
||||
// Control: the other states don't ask.
|
||||
assert!(check_confirmed(AgentState::Paused, false).is_ok());
|
||||
}
|
||||
|
||||
/// The whole point of parsing problem+json: the 400 for an unknown
|
||||
/// hive carries the roster of hives that would have worked, and that
|
||||
|
|
|
|||
Loading…
Reference in a new issue