@@ -6,6 +6,8 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
churn-domain.workspace = true
|
||||
|
||||
anyhow.workspace = true
|
||||
tokio.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -13,3 +15,6 @@ tracing-subscriber.workspace = true
|
||||
clap.workspace = true
|
||||
dotenv.workspace = true
|
||||
axum.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
reqwest.workspace = true
|
||||
|
85
crates/churn-agent/src/agent.rs
Normal file
85
crates/churn-agent/src/agent.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
use axum::{async_trait};
|
||||
use churn_domain::{ServerEnrollReq};
|
||||
|
||||
|
||||
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AgentService(Arc<dyn AgentServiceTrait + Send + Sync + 'static>);
|
||||
|
||||
impl std::ops::Deref for AgentService {
|
||||
type Target = Arc<dyn AgentServiceTrait + Send + Sync + 'static>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AgentService {
|
||||
fn default() -> Self {
|
||||
Self(Arc::new(DefaultAgentService::default()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct DefaultAgentService {
|
||||
server: Arc<Mutex<String>>,
|
||||
leases: Arc<Mutex<Vec<String>>>,
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[async_trait]
|
||||
pub trait AgentServiceTrait {
|
||||
async fn enroll(&self, agent_name: &str, server: &str, lease: &str) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AgentServiceTrait for DefaultAgentService {
|
||||
async fn enroll(&self, agent_name: &str, server: &str, lease: &str) -> anyhow::Result<()> {
|
||||
let mut cur_server = self.server.lock().await;
|
||||
let mut leases = self.leases.lock().await;
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let req = client
|
||||
.post(format!("{server}/agent/enroll"))
|
||||
.json(&ServerEnrollReq {
|
||||
lease: lease.into(),
|
||||
agent_name: agent_name.into(),
|
||||
})
|
||||
.build()?;
|
||||
|
||||
let resp = client.execute(req).await?;
|
||||
if !resp.status().is_success() {
|
||||
if let Ok(text) = resp.text().await {
|
||||
anyhow::bail!(
|
||||
"could not enroll agent: {} at server: {}, error: {}",
|
||||
agent_name,
|
||||
server,
|
||||
text
|
||||
)
|
||||
}
|
||||
|
||||
anyhow::bail!(
|
||||
"could not enroll agent: {} at server: {}",
|
||||
agent_name,
|
||||
server
|
||||
)
|
||||
}
|
||||
|
||||
*cur_server = server.to_string();
|
||||
leases.push(lease.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@@ -1,7 +1,19 @@
|
||||
mod agent;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use axum::{response::IntoResponse, routing::get, Router};
|
||||
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;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None, subcommand_required = true)]
|
||||
@@ -30,6 +42,14 @@ enum Commands {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Default)]
|
||||
struct AppState {
|
||||
agent: AgentService,
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
dotenv::dotenv().ok();
|
||||
@@ -47,7 +67,10 @@ async fn handle_command(cmd: Command) -> anyhow::Result<()> {
|
||||
Some(Commands::Daemon { host }) => {
|
||||
tracing::info!("Starting churn server");
|
||||
|
||||
let app = Router::new().route("/ping", get(ping));
|
||||
let app = Router::new()
|
||||
.route("/enroll", post(enroll))
|
||||
.route("/ping", get(ping))
|
||||
.with_state(AppState::default());
|
||||
|
||||
tracing::info!("churn server listening on {}", host);
|
||||
axum::Server::bind(&host)
|
||||
@@ -58,14 +81,52 @@ async fn handle_command(cmd: Command) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
Some(Commands::Connect {
|
||||
host,
|
||||
token,
|
||||
agent_name,
|
||||
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(())
|
||||
}
|
||||
|
Reference in New Issue
Block a user