use anyhow::{Result, Context};
use colored::Colorize;
use std::collections::HashMap;
use revoke_core::ConfigProvider;
use revoke_config::ConfigChange as ConfigChangeType;
use crate::config::Config;
use crate::commands::ConfigCommands;
use crate::utils::{print_success, print_error, print_info};
use futures::StreamExt;
pub async fn handle_command(command: ConfigCommands, config: &Config) -> Result<()> {
let provider = create_config_provider(config).await?;
match command {
ConfigCommands::Get { key } => {
get_config(&provider, &key).await?
}
ConfigCommands::Set { key, value } => {
set_config(&provider, &key, &value).await?
}
ConfigCommands::Delete { key } => {
delete_config(&provider, &key).await?
}
ConfigCommands::List { prefix } => {
list_configs(&provider, prefix).await?
}
ConfigCommands::Watch { key } => {
watch_config(&provider, &key).await?
}
ConfigCommands::Export { output, format } => {
export_config(&provider, output, &format).await?
}
ConfigCommands::Import { file, format } => {
import_config(&provider, &file, format).await?
}
}
Ok(())
}
async fn create_config_provider(config: &Config) -> Result<Box<dyn ConfigProvider>> {
use revoke_config::{ConsulConfigProvider, consul::ConsulConfigOptions};
use std::time::Duration;
let options = ConsulConfigOptions {
address: config.consul_address.clone(),
namespace: Some(config.config_namespace.clone()),
watch_interval: Duration::from_secs(5),
..Default::default()
};
let provider = ConsulConfigProvider::new(options);
let provider_clone = provider.clone();
tokio::spawn(async move {
provider_clone.start_watch_loop().await;
});
Ok(Box::new(provider))
}
async fn get_config(provider: &Box<dyn ConfigProvider>, key: &str) -> Result<()> {
match provider.get(key).await {
Ok(value) => {
println!("{}: {}", key.bold(), value);
}
Err(_) => {
print_error(&format!("Configuration key '{}' not found", key));
}
}
Ok(())
}
async fn set_config(provider: &Box<dyn ConfigProvider>, key: &str, value: &str) -> Result<()> {
provider.set(key, value).await?;
print_success(&format!("Configuration '{}' set successfully", key));
Ok(())
}
async fn delete_config(provider: &Box<dyn ConfigProvider>, key: &str) -> Result<()> {
provider.set(key, "").await?;
print_success(&format!("Configuration '{}' deleted successfully", key));
Ok(())
}
async fn list_configs(provider: &Box<dyn ConfigProvider>, prefix: Option<String>) -> Result<()> {
print_info("Listing configurations from Consul...");
if let Some(prefix) = prefix {
print_info(&format!("Please use Consul UI to list configurations with prefix: {}", prefix));
} else {
print_info("Please use Consul UI at http://localhost:8500 to list all configurations");
}
Ok(())
}
async fn watch_config(provider: &Box<dyn ConfigProvider>, key: &str) -> Result<()> {
print_info(&format!("Watching configuration key: {}", key));
println!("Press Ctrl+C to stop watching\n");
let mut stream = provider.watch(key).await?;
while let Some(value) = stream.next().await {
println!("{} {} {}: {}",
chrono::Local::now().format("%H:%M:%S"),
"CHANGED".yellow().bold(),
key,
value
);
}
Ok(())
}
async fn export_config(
provider: &Box<dyn ConfigProvider>,
output: Option<String>,
format: &str,
) -> Result<()> {
print_info("Export is not directly supported through the ConfigProvider trait");
print_info("Please use Consul's export features or API directly");
Ok(())
}
async fn import_config(
provider: &Box<dyn ConfigProvider>,
file: &str,
format: Option<String>,
) -> Result<()> {
let content = std::fs::read_to_string(file)
.context("Failed to read configuration file")?;
let format = format.unwrap_or_else(|| {
if file.ends_with(".json") {
"json".to_string()
} else if file.ends_with(".yaml") || file.ends_with(".yml") {
"yaml".to_string()
} else if file.ends_with(".toml") {
"toml".to_string()
} else {
"json".to_string()
}
});
let configs: HashMap<String, serde_json::Value> = match format.as_str() {
"json" => serde_json::from_str(&content)?,
"yaml" => serde_yaml::from_str(&content)?,
"toml" => toml::from_str(&content)?,
_ => return Err(anyhow::anyhow!("Unsupported import format: {}", format)),
};
let total = configs.len();
let mut imported = 0;
for (key, value) in configs {
let value_str = match value {
serde_json::Value::String(s) => s,
v => v.to_string(),
};
match provider.set(&key, &value_str).await {
Ok(_) => imported += 1,
Err(e) => print_error(&format!("Failed to import '{}': {}", key, e)),
}
}
print_success(&format!(
"Imported {} out of {} configurations from {}",
imported, total, file
));
Ok(())
}
pub use revoke_config::ConfigChange;