[Wip] CLI & Daemon
This commit is contained in:
parent
3bc5cc959a
commit
6a535099bb
44 changed files with 4417 additions and 303 deletions
|
|
@ -1,10 +1,16 @@
|
|||
use crate::{DaemonRuntime, DaemonServices};
|
||||
use iota_ipc::{ExitIntent, IpcErrorCode, LocalRequest, ResponseEnvelope, ResponseResult};
|
||||
use crate::log_buffer::LogBuffer;
|
||||
use iota_ipc::{
|
||||
ComponentStatusResponse, CommunitySummary, ConfigResponse, ExitIntent, IpcErrorCode,
|
||||
LogEntriesResponse, LocalRequest, OmikronStatusResponse, ResponseEnvelope, ResponsePayload,
|
||||
ResponseResult, StatusResponse, TaskSummary, UpdateStatusResponse, UserDetailResponse,
|
||||
UserSummary,
|
||||
};
|
||||
use iota_logger::{log, log_command};
|
||||
use iota_storage::users::user_manager;
|
||||
use iota_storage::util::config_util::modify_config;
|
||||
use iota_storage::util::config_util::{self, modify_config};
|
||||
use mtp::codec::{CommunicationType, CommunicationValue};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::daemon_state::{ShutdownReason, StartupPhase};
|
||||
|
|
@ -13,11 +19,12 @@ use crate::daemon_state::{ShutdownReason, StartupPhase};
|
|||
pub struct CommandRouter {
|
||||
runtime: Arc<DaemonRuntime>,
|
||||
services: Arc<DaemonServices>,
|
||||
log_buffer: Arc<Mutex<LogBuffer>>,
|
||||
}
|
||||
|
||||
impl CommandRouter {
|
||||
pub fn new(runtime: Arc<DaemonRuntime>, services: Arc<DaemonServices>) -> Self {
|
||||
Self { runtime, services }
|
||||
pub fn new(runtime: Arc<DaemonRuntime>, services: Arc<DaemonServices>, log_buffer: Arc<Mutex<LogBuffer>>) -> Self {
|
||||
Self { runtime, services, log_buffer }
|
||||
}
|
||||
|
||||
pub async fn route(&self, request_id: u64, request: LocalRequest) -> ResponseEnvelope {
|
||||
|
|
@ -26,35 +33,6 @@ impl CommandRouter {
|
|||
ResponseEnvelope { request_id, result }
|
||||
}
|
||||
|
||||
/// Parse a legacy console command string into a typed request.
|
||||
pub fn parse_console_command(line: &str) -> Option<LocalRequest> {
|
||||
let parts: Vec<&str> = line.trim_start_matches('/').split_whitespace().collect();
|
||||
match parts.as_slice() {
|
||||
["help"] => None,
|
||||
["tasks"] => Some(LocalRequest::ListTasks),
|
||||
["ping", _] | ["ping"] => None,
|
||||
["user", "add", username] => Some(LocalRequest::CreateUser {
|
||||
username: username.to_string(),
|
||||
}),
|
||||
["user", "remove", username] => {
|
||||
let user = user_manager::get_user_by_username(username)?;
|
||||
Some(LocalRequest::RemoveUser {
|
||||
user_id: user.user_id,
|
||||
})
|
||||
}
|
||||
["user", "list"] => Some(LocalRequest::ListUsers),
|
||||
["reconnect"] => Some(LocalRequest::ReconnectOmikron),
|
||||
["regenerate", "keys"] => Some(LocalRequest::RotateIotaIdentity),
|
||||
["reload"] | ["restart"] => Some(LocalRequest::RequestProcessExit {
|
||||
intent: ExitIntent::Restart,
|
||||
}),
|
||||
["shutdown"] | ["stop"] => Some(LocalRequest::RequestProcessExit {
|
||||
intent: ExitIntent::Stop,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn execute(&self, request: LocalRequest) -> ResponseResult {
|
||||
let needs_omikron = matches!(
|
||||
request,
|
||||
|
|
@ -83,28 +61,33 @@ impl CommandRouter {
|
|||
.iter()
|
||||
.map(|task| task.to_string())
|
||||
.collect();
|
||||
let mut info = format!("Phase: {:?}, Tasks: {}", phase, tasks.join(", "));
|
||||
if let Some(reason) = degraded {
|
||||
info.push_str(&format!(", Degraded: {}", reason));
|
||||
}
|
||||
ResponseResult::Ok(info)
|
||||
ResponseResult::Ok(ResponsePayload::Status(StatusResponse {
|
||||
phase: format!("{:?}", phase),
|
||||
tasks: tasks.clone(),
|
||||
degraded_reason: degraded,
|
||||
}))
|
||||
}
|
||||
LocalRequest::ListTasks => {
|
||||
let tasks: Vec<String> = self
|
||||
let tasks: Vec<TaskSummary> = self
|
||||
.runtime
|
||||
.state
|
||||
.active_tasks
|
||||
.iter()
|
||||
.map(|task| task.to_string())
|
||||
.map(|task| TaskSummary {
|
||||
name: task.to_string(),
|
||||
})
|
||||
.collect();
|
||||
ResponseResult::Ok(tasks.join(", "))
|
||||
ResponseResult::Ok(ResponsePayload::Tasks(tasks))
|
||||
}
|
||||
LocalRequest::ListUsers => {
|
||||
let users: Vec<String> = user_manager::get_users()
|
||||
let users: Vec<UserSummary> = user_manager::get_users()
|
||||
.into_iter()
|
||||
.map(|user| format!("{} ({})", user.username, user.user_id))
|
||||
.map(|user| UserSummary {
|
||||
user_id: user.user_id,
|
||||
username: user.username,
|
||||
})
|
||||
.collect();
|
||||
ResponseResult::Ok(users.join("\n"))
|
||||
ResponseResult::Ok(ResponsePayload::Users(users))
|
||||
}
|
||||
LocalRequest::CreateUser { username } => {
|
||||
match omikron_connector::user_ops::create_user(
|
||||
|
|
@ -113,7 +96,12 @@ impl CommandRouter {
|
|||
)
|
||||
.await
|
||||
{
|
||||
(Some(user), _) => ResponseResult::Ok(format!("Created user {}", user.user_id)),
|
||||
(Some(user), _) => {
|
||||
ResponseResult::Ok(ResponsePayload::UserCreated {
|
||||
user_id: user.user_id,
|
||||
username: user.username,
|
||||
})
|
||||
}
|
||||
_ => ResponseResult::Error(IpcErrorCode::StorageFailure),
|
||||
}
|
||||
}
|
||||
|
|
@ -128,10 +116,12 @@ impl CommandRouter {
|
|||
return ResponseResult::Error(IpcErrorCode::OmikronUnavailable);
|
||||
}
|
||||
user_manager::remove_user(user.user_id);
|
||||
ResponseResult::Ok(format!("Removed user {}", user.user_id))
|
||||
ResponseResult::Ok(ResponsePayload::UserRemoved { user_id })
|
||||
}
|
||||
LocalRequest::ReconnectOmikron => match self.services.omikron.reconnect().await {
|
||||
Ok(()) => ResponseResult::Ok("Reconnected to Omikron server".into()),
|
||||
Ok(()) => ResponseResult::Ok(ResponsePayload::Acknowledged {
|
||||
message: "Reconnected to Omikron server".into(),
|
||||
}),
|
||||
Err(_) => ResponseResult::Error(IpcErrorCode::OmikronUnavailable),
|
||||
},
|
||||
LocalRequest::RotateIotaIdentity => {
|
||||
|
|
@ -141,9 +131,9 @@ impl CommandRouter {
|
|||
config.iota_id = None;
|
||||
});
|
||||
match self.services.omikron.reconnect().await {
|
||||
Ok(()) => ResponseResult::Ok(
|
||||
"Key pair regenerated and Omikron reconnection requested".into(),
|
||||
),
|
||||
Ok(()) => ResponseResult::Ok(ResponsePayload::Acknowledged {
|
||||
message: "Key pair regenerated and Omikron reconnection requested".into(),
|
||||
}),
|
||||
Err(_) => ResponseResult::Error(IpcErrorCode::OmikronUnavailable),
|
||||
}
|
||||
}
|
||||
|
|
@ -160,18 +150,120 @@ impl CommandRouter {
|
|||
ExitIntent::Stop => ShutdownReason::Stop,
|
||||
ExitIntent::Restart => ShutdownReason::Restart,
|
||||
});
|
||||
ResponseResult::Ok("process exit accepted".into())
|
||||
ResponseResult::Ok(ResponsePayload::Acknowledged {
|
||||
message: "process exit accepted".into(),
|
||||
})
|
||||
}
|
||||
LocalRequest::GetDaemonStatus => {
|
||||
ResponseResult::Ok(format!("{:?}", self.runtime.snapshot()))
|
||||
ResponseResult::Ok(ResponsePayload::DaemonStatus(
|
||||
iota_ipc::DaemonStatusResponse {
|
||||
formatted: format!("{:?}", self.runtime.snapshot()),
|
||||
},
|
||||
))
|
||||
}
|
||||
LocalRequest::RestartDaemon => {
|
||||
self.runtime.shutdown(ShutdownReason::Restart);
|
||||
ResponseResult::Ok("Daemon restart requested".into())
|
||||
ResponseResult::Ok(ResponsePayload::Acknowledged {
|
||||
message: "Daemon restart requested".into(),
|
||||
})
|
||||
}
|
||||
LocalRequest::StopDaemon => {
|
||||
self.runtime.shutdown(ShutdownReason::Stop);
|
||||
ResponseResult::Ok("Daemon shutdown requested".into())
|
||||
ResponseResult::Ok(ResponsePayload::Acknowledged {
|
||||
message: "Daemon shutdown requested".into(),
|
||||
})
|
||||
}
|
||||
LocalRequest::GetConfig => {
|
||||
let cfg = config_util::CONFIG.load();
|
||||
let yaml = serde_yaml::to_string(&**cfg).unwrap_or_default();
|
||||
ResponseResult::Ok(ResponsePayload::Config(ConfigResponse { yaml }))
|
||||
}
|
||||
LocalRequest::SetConfig { key, value } => {
|
||||
match config_util::modify_config_value(&key, &value) {
|
||||
Ok(()) => ResponseResult::Ok(ResponsePayload::Acknowledged {
|
||||
message: format!("Set {key} = {value}"),
|
||||
}),
|
||||
Err(_e) => ResponseResult::Error(IpcErrorCode::InvalidRequest),
|
||||
}
|
||||
}
|
||||
LocalRequest::ReloadConfig => {
|
||||
config_util::load_config();
|
||||
ResponseResult::Ok(ResponsePayload::Acknowledged {
|
||||
message: "Configuration reloaded".into(),
|
||||
})
|
||||
}
|
||||
LocalRequest::GetOmikronStatus => {
|
||||
let connected = self.services.omikron.is_connected().await;
|
||||
let iota_id = config_util::CONFIG.load().iota_id;
|
||||
ResponseResult::Ok(ResponsePayload::OmikronStatus(
|
||||
OmikronStatusResponse {
|
||||
connected,
|
||||
iota_id,
|
||||
},
|
||||
))
|
||||
}
|
||||
LocalRequest::ListComponents => {
|
||||
let snapshot = self.runtime.snapshot();
|
||||
let components: Vec<ComponentStatusResponse> = snapshot
|
||||
.components
|
||||
.into_iter()
|
||||
.map(|(id, health)| ComponentStatusResponse {
|
||||
id,
|
||||
status: health.status,
|
||||
message: health.message,
|
||||
})
|
||||
.collect();
|
||||
ResponseResult::Ok(ResponsePayload::Components(components))
|
||||
}
|
||||
LocalRequest::GetUser { user_id } => {
|
||||
match user_manager::get_user(user_id) {
|
||||
Some(user) => ResponseResult::Ok(ResponsePayload::UserDetail(
|
||||
UserDetailResponse {
|
||||
user_id: user.user_id,
|
||||
username: user.username,
|
||||
display_name: user.display_name,
|
||||
created_at: user.created_at,
|
||||
trusted_apps: user.trusted_apps.keys().cloned().collect(),
|
||||
},
|
||||
)),
|
||||
None => ResponseResult::Error(IpcErrorCode::NotFound),
|
||||
}
|
||||
}
|
||||
LocalRequest::ImportUser { username } => {
|
||||
match user_manager::load_from_tu(&username).await {
|
||||
Ok(()) => ResponseResult::Ok(ResponsePayload::Acknowledged {
|
||||
message: format!("Imported user {username}"),
|
||||
}),
|
||||
Err(()) => ResponseResult::Error(IpcErrorCode::StorageFailure),
|
||||
}
|
||||
}
|
||||
LocalRequest::GetLogs { limit } => {
|
||||
let entries = if let Ok(buf) = self.log_buffer.lock() {
|
||||
buf.recent(limit)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
ResponseResult::Ok(ResponsePayload::LogEntries(LogEntriesResponse { entries }))
|
||||
}
|
||||
LocalRequest::CheckUpdate => {
|
||||
match iota_updater::check_update().await {
|
||||
Ok(available) => ResponseResult::Ok(ResponsePayload::UpdateStatus(
|
||||
UpdateStatusResponse { available },
|
||||
)),
|
||||
Err(_e) => ResponseResult::Error(IpcErrorCode::InternalFailure),
|
||||
}
|
||||
}
|
||||
LocalRequest::ListCommunities => {
|
||||
let iota_id = config_util::CONFIG.load().iota_id.map(|id| id as i64).unwrap_or(0);
|
||||
let stored = iota_storage::util::communities_util::CommunitiesUtil::get_communities(iota_id);
|
||||
let summaries: Vec<CommunitySummary> = stored
|
||||
.into_iter()
|
||||
.map(|c| CommunitySummary {
|
||||
name: c.address,
|
||||
title: c.title,
|
||||
})
|
||||
.collect();
|
||||
ResponseResult::Ok(ResponsePayload::Communities(summaries))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue