churn/crates/churn-agent/src/main.rs

131 lines
2.9 KiB
Rust
Raw Normal View History

mod agent;
use std::net::SocketAddr;
use agent::AgentService;
use anyhow::Error;
use axum::{
extract::State,
http::StatusCode,
response::{IntoResponse, Response},
routing::{get, post},
Json, Router,
};
use churn_domain::AgentEnrollReq;
use clap::{Parser, Subcommand};
use serde_json::json;
use tokio::net::TcpListener;
#[derive(Parser)]
#[command(author, version, about, long_about = None, subcommand_required = true)]
struct Command {
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
Daemon {
#[arg(env = "CHURN_ADDR", long)]
host: SocketAddr,
},
Connect {
/// agent name is the hostname which other agents or servers can resolve and connect via. It should be unique
#[arg(env = "CHURN_AGENT_NAME", long)]
agent_name: String,
#[arg(env = "CHURN_ADDR", long)]
host: SocketAddr,
#[arg(env = "CHURN_TOKEN", long)]
token: String,
},
}
#[derive(Clone, Default)]
struct AppState {
agent: AgentService,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
dotenv::dotenv().ok();
tracing_subscriber::fmt::init();
let cli = Command::parse();
handle_command(cli).await?;
Ok(())
}
async fn handle_command(cmd: Command) -> anyhow::Result<()> {
match cmd.command {
Some(Commands::Daemon { host }) => {
tracing::info!("Starting churn server");
let app = Router::new()
.route("/enroll", post(enroll))
.route("/ping", get(ping))
.with_state(AppState::default());
tracing::info!("churn server listening on {}", host);
let listener = TcpListener::bind(&host).await?;
axum::serve(listener, app.into_make_service())
.await
.unwrap();
Ok(())
}
Some(Commands::Connect {
host: _,
token: _,
agent_name: _,
}) => todo!(),
None => todo!(),
}
}
enum AppError {
Internal(Error),
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, error_message) = match self {
AppError::Internal(e) => {
tracing::error!("failed with error: {}", e);
(
StatusCode::INTERNAL_SERVER_ERROR,
"failed with internal error",
)
}
};
let body = Json(json!({
"error": error_message,
}));
(status, body).into_response()
}
}
async fn ping() -> impl IntoResponse {
"pong!"
}
async fn enroll(
State(state): State<AppState>,
Json(req): Json<AgentEnrollReq>,
) -> Result<(), AppError> {
state
.agent
.enroll(&req.agent_name, &req.server, &req.lease)
.await
.map_err(AppError::Internal)?;
Ok(())
}