From 142fb0fb17a090d2b1c58cac7c2ed32a7b425149 Mon Sep 17 00:00:00 2001 From: Meshiest Date: Mon, 9 Sep 2024 19:59:00 -0400 Subject: [PATCH 1/2] feat(snops): add private_key, env, and binary config actions --- Cargo.lock | 1 + crates/snops-cli/Cargo.toml | 1 + .../snops-cli/src/commands/env/action/mod.rs | 53 ++++++++++++++++++- crates/snops-common/src/action_models.rs | 14 +++-- crates/snops-common/src/state/node_state.rs | 4 ++ crates/snops/src/server/actions/config.rs | 41 ++++++++++++-- 6 files changed, 105 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73eed79c..1af0f247 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4539,6 +4539,7 @@ dependencies = [ "serde", "serde_json", "snops-common", + "thiserror", ] [[package]] diff --git a/crates/snops-cli/Cargo.toml b/crates/snops-cli/Cargo.toml index 1da2a217..c081e37a 100644 --- a/crates/snops-cli/Cargo.toml +++ b/crates/snops-cli/Cargo.toml @@ -18,6 +18,7 @@ reqwest = { workspace = true, features = ["blocking", "json"] } serde.workspace = true serde_json.workspace = true snops-common = { workspace = true, features = ["aot_cmds"] } +thiserror.workspace = true [build-dependencies] anyhow.workspace = true diff --git a/crates/snops-cli/src/commands/env/action/mod.rs b/crates/snops-cli/src/commands/env/action/mod.rs index 83606250..6bfe6efb 100644 --- a/crates/snops-cli/src/commands/env/action/mod.rs +++ b/crates/snops-cli/src/commands/env/action/mod.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{collections::HashMap, str::FromStr}; use anyhow::Result; use clap::Parser; @@ -9,7 +9,7 @@ use snops_common::{ action_models::{AleoValue, WithTargets}, key_source::KeySource, node_targets::{NodeTarget, NodeTargetError, NodeTargets}, - state::{CannonId, DocHeightRequest, EnvId}, + state::{CannonId, DocHeightRequest, EnvId, InternedId}, }; //scli env canary action online client/* @@ -132,12 +132,42 @@ pub enum Action { /// Configure the validators of the target nodes, or `none`. #[clap(long, short)] validators: Option, + /// Set environment variables for a node: `--env FOO=bar` + #[clap(long, short, number_of_values = 1, value_parser = clap::value_parser!(KeyEqValue))] + env: Option>, + // Remove environment variables from a node: `--del-env FOO,BAR` + #[clap(long, short, value_delimiter = ',', allow_hyphen_values = true)] + del_env: Option>, /// The nodes to configure. #[clap(num_args = 1, value_delimiter = ' ')] nodes: Vec, + /// Configure the binary for a node. + #[clap(long, short)] + binary: Option, + /// Configure the private key for a node. + #[clap(long, short)] + private_key: Option, }, } +#[derive(Clone, Debug)] +pub struct KeyEqValue(pub String, pub String); + +impl FromStr for KeyEqValue { + type Err = &'static str; + + fn from_str(s: &str) -> std::result::Result { + let (l, r) = s.split_once('=').ok_or("missing = in `key=value`")?; + Ok(Self(l.to_owned(), r.to_owned())) + } +} + +impl KeyEqValue { + pub fn pair(self) -> (String, String) { + (self.0, self.1) + } +} + impl Action { pub fn execute(self, url: &str, env_id: EnvId, client: Client) -> Result { use Action::*; @@ -251,6 +281,10 @@ impl Action { peers, validators, nodes, + env, + del_env, + binary, + private_key, } => { let ep = format!("{url}/api/v1/env/{env_id}/action/config"); @@ -270,6 +304,21 @@ impl Action { if let Some(validators) = validators { json["validators"] = json!(NodeTargets::from(validators)); } + if let Some(binary) = binary { + json["binary"] = json!(binary); + } + if let Some(private_key) = private_key { + json["private_key"] = json!(private_key); + } + if let Some(env) = env { + json["set_env"] = json!(env + .into_iter() + .map(KeyEqValue::pair) + .collect::>()) + } + if let Some(del_env) = del_env { + json["del_env"] = json!(del_env) + } // this api accepts a list of json objects client.post(ep).json(&json!(vec![json])).send()? diff --git a/crates/snops-common/src/action_models.rs b/crates/snops-common/src/action_models.rs index 7b05623a..5e8e950b 100644 --- a/crates/snops-common/src/action_models.rs +++ b/crates/snops-common/src/action_models.rs @@ -1,11 +1,12 @@ use std::str::FromStr; +use indexmap::{IndexMap, IndexSet}; use serde::{Deserialize, Serialize}; use crate::{ key_source::KeySource, node_targets::{NodeTarget, NodeTargets}, - state::{CannonId, DocHeightRequest}, + state::{CannonId, DocHeightRequest, InternedId}, }; #[derive(Deserialize, Serialize, Clone)] @@ -120,7 +121,12 @@ pub struct Reconfig { pub peers: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub validators: Option, - // TODO: private key - // TODO: env - // TODO: binary + #[serde(default, skip_serializing_if = "Option::is_none")] + pub binary: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub private_key: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub set_env: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub del_env: Option>, } diff --git a/crates/snops-common/src/state/node_state.rs b/crates/snops-common/src/state/node_state.rs index 8653152a..81d2a10b 100644 --- a/crates/snops-common/src/state/node_state.rs +++ b/crates/snops-common/src/state/node_state.rs @@ -162,6 +162,10 @@ impl KeyState { _ => None, } } + + pub fn is_none(&self) -> bool { + matches!(self, KeyState::None) + } } #[derive( diff --git a/crates/snops/src/server/actions/config.rs b/crates/snops/src/server/actions/config.rs index 8a7174cf..777b714c 100644 --- a/crates/snops/src/server/actions/config.rs +++ b/crates/snops/src/server/actions/config.rs @@ -6,7 +6,7 @@ use axum::{ }; use snops_common::{ action_models::{Reconfig, WithTargets}, - state::{AgentId, AgentState}, + state::{AgentId, AgentState, InternedId}, }; use super::Env; @@ -31,7 +31,7 @@ pub async fn config( AgentState::Inventory => (), AgentState::Node(_, ref mut n) => { $({ - let $key = &n.$key; + let $key = &mut n.$key; n.$key = $val; })* } @@ -43,7 +43,7 @@ pub async fn config( $agent.client_owned(), $agent.state().clone().map_node(|mut n| { $({ - let $key = &n.$key; + let $key = &mut n.$key; n.$key = $val; })* n @@ -78,6 +78,41 @@ pub async fn config( .collect::>(); set_node_field!(agent, validators = v.clone()); } + + if let Some(b) = &data.binary { + set_node_field!(agent, binary = (*b != InternedId::default()).then_some(*b)); + } + + if let Some(k) = &data.private_key { + let key = env.storage.sample_keysource_pk(k); + if key.is_none() { + return ServerError::NotFound(format!("key source not found: `{k}`")) + .into_response(); + } + set_node_field!(agent, private_key = key.clone()); + } + + // inject env fields + if let Some(e) = &data.set_env { + set_node_field!( + agent, + env = { + env.extend(e.clone()); + std::mem::take(env) + } + ) + } + + // remove env fields + if let Some(e) = &data.del_env { + set_node_field!( + agent, + env = { + env.retain(|k, _| !e.contains(k)); + std::mem::take(env) + } + ) + } } } From ed9c41be7616a87aa504ca2d7fb9efff0be716d7 Mon Sep 17 00:00:00 2001 From: Meshiest Date: Tue, 10 Sep 2024 17:18:01 -0400 Subject: [PATCH 2/2] fix(agent): fix broadcast and snarkos_get RPCs failing for specified bind addresses --- crates/snops-agent/src/cli.rs | 8 ++++++++ crates/snops-agent/src/metrics/mod.rs | 9 ++------- crates/snops-agent/src/rpc/control.rs | 6 ++++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/crates/snops-agent/src/cli.rs b/crates/snops-agent/src/cli.rs index 1f69baf2..033b1619 100644 --- a/crates/snops-agent/src/cli.rs +++ b/crates/snops-agent/src/cli.rs @@ -101,6 +101,14 @@ impl Cli { std::process::exit(0); } + pub fn get_local_ip(&self) -> IpAddr { + if self.bind_addr.is_unspecified() { + IpAddr::V4(Ipv4Addr::LOCALHOST) + } else { + self.bind_addr + } + } + pub fn endpoint_and_uri(&self) -> (String, Uri) { // get the endpoint let endpoint = self diff --git a/crates/snops-agent/src/metrics/mod.rs b/crates/snops-agent/src/metrics/mod.rs index e35d6728..1dd66b21 100644 --- a/crates/snops-agent/src/metrics/mod.rs +++ b/crates/snops-agent/src/metrics/mod.rs @@ -1,11 +1,6 @@ pub mod tps; -use std::{ - collections::HashMap, - net::{IpAddr, Ipv4Addr, SocketAddr}, - sync::Arc, - time::Duration, -}; +use std::{collections::HashMap, net::SocketAddr, sync::Arc, time::Duration}; use self::tps::TpsMetric; use crate::state::GlobalState; @@ -39,7 +34,7 @@ pub fn init(state: Arc) { let response = match client .get(format!( "http://{}/", - SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), state.cli.ports.metrics) + SocketAddr::new(state.cli.get_local_ip(), state.cli.ports.metrics) )) .send() .await diff --git a/crates/snops-agent/src/rpc/control.rs b/crates/snops-agent/src/rpc/control.rs index 33e04f57..76d76ade 100644 --- a/crates/snops-agent/src/rpc/control.rs +++ b/crates/snops-agent/src/rpc/control.rs @@ -428,7 +428,8 @@ impl AgentService for AgentRpcServer { .network; let url = format!( - "http://127.0.0.1:{}/{network}{route}", + "http://{}:{}/{network}{route}", + self.state.cli.get_local_ip(), self.state.cli.ports.rest ); let response = reqwest::get(&url) @@ -460,7 +461,8 @@ impl AgentService for AgentRpcServer { .network; let url = format!( - "http://127.0.0.1:{}/{network}/transaction/broadcast", + "http://{}:{}/{network}/transaction/broadcast", + self.state.cli.get_local_ip(), self.state.cli.ports.rest ); let response = reqwest::Client::new()