use anyhow::Result;
use clap::Parser;
use sentinel_agent_api_deprecation::{ApiDeprecationAgent, ApiDeprecationConfig};
use sentinel_agent_sdk::AgentRunner;
use std::path::PathBuf;
use tracing::{info, Level};
use tracing_subscriber::FmtSubscriber;
#[derive(Parser, Debug)]
#[command(
name = "sentinel-agent-api-deprecation",
about = "API deprecation management agent for Sentinel proxy",
version
)]
struct Args {
#[arg(short, long, default_value = "api-deprecation.yaml")]
config: PathBuf,
#[arg(short, long, default_value = "/tmp/sentinel-api-deprecation.sock")]
socket: PathBuf,
#[arg(short = 'L', long, default_value = "info")]
log_level: Level,
#[arg(long)]
print_config: bool,
#[arg(long)]
validate: bool,
#[arg(long)]
metrics: bool,
#[arg(long, default_value = "9090")]
metrics_port: u16,
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
let subscriber = FmtSubscriber::builder()
.with_max_level(args.log_level)
.with_target(false)
.finish();
tracing::subscriber::set_global_default(subscriber)?;
if args.print_config {
let default_config = include_str!("../examples/default-config.yaml");
println!("{}", default_config);
return Ok(());
}
let config = if args.config.exists() {
info!(path = ?args.config, "Loading configuration");
ApiDeprecationConfig::from_file(&args.config)?
} else if args.validate {
anyhow::bail!("Configuration file not found: {:?}", args.config);
} else {
info!("Using default configuration");
ApiDeprecationConfig::default()
};
if args.validate {
config.validate()?;
println!("Configuration is valid");
return Ok(());
}
let agent = ApiDeprecationAgent::new(config);
if args.metrics {
let metrics = agent.metrics().clone();
let port = args.metrics_port;
tokio::spawn(async move {
start_metrics_server(metrics, port).await;
});
}
info!(
socket = ?args.socket,
"Starting API deprecation agent"
);
AgentRunner::new(agent)
.with_name("api-deprecation")
.with_socket(&args.socket)
.run()
.await?;
Ok(())
}
async fn start_metrics_server(metrics: sentinel_agent_api_deprecation::metrics::DeprecationMetrics, port: u16) {
use tokio::io::AsyncWriteExt;
use tokio::net::TcpListener;
let listener = match TcpListener::bind(format!("0.0.0.0:{}", port)).await {
Ok(l) => l,
Err(e) => {
tracing::error!(error = %e, "Failed to start metrics server");
return;
}
};
info!(port = port, "Metrics server started");
loop {
match listener.accept().await {
Ok((mut socket, _)) => {
let output = metrics.encode();
let response = format!(
"HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: {}\r\n\r\n{}",
output.len(),
output
);
let _ = socket.write_all(response.as_bytes()).await;
}
Err(e) => {
tracing::warn!(error = %e, "Failed to accept metrics connection");
}
}
}
}