Some checks failed
CI/CD Pipeline / unit-tests (push) Failing after 1m16s
CI/CD Pipeline / integration-tests (push) Failing after 2m32s
CI/CD Pipeline / lint (push) Successful in 5m22s
CI/CD Pipeline / e2e-tests (push) Has been skipped
CI/CD Pipeline / build (push) Has been skipped
131 lines
4.6 KiB
Rust
131 lines
4.6 KiB
Rust
### /Users/vlad/Developer/madapes/madbase/gateway/src/control.rs
|
|
```rust
|
|
1: use axum::{
|
|
2: extract::{Request, Query},
|
|
3: middleware::{from_fn, Next},
|
|
4: response::{Response, IntoResponse},
|
|
5: routing::get,
|
|
6: Router,
|
|
7: };
|
|
8: use axum::http::StatusCode;
|
|
9: use axum_prometheus::PrometheusMetricLayer;
|
|
10: use common::{init_pool, Config};
|
|
11: use sqlx::PgPool;
|
|
12: use crate::admin_auth::admin_auth_middleware;
|
|
13: use std::collections::HashMap;
|
|
14: use std::net::SocketAddr;
|
|
15: use std::time::Duration;
|
|
16: use tower_http::services::ServeDir;
|
|
17: use tower_http::cors::{AllowOrigin, CorsLayer};
|
|
use axum::http::{HeaderMap, HeaderValue, Method};
|
|
use axum::http::header;
|
|
18: use tower_http::trace::TraceLayer;
|
|
19:
|
|
20: async fn logs_proxy_handler(
|
|
21: Query(params): Query<HashMap<String, String>>,
|
|
22: ) -> impl IntoResponse {
|
|
23: let client = reqwest::Client::new();
|
|
24: let loki_url = std::env::var("LOKI_URL")
|
|
25: .unwrap_or_else(|_| "http://loki:3100".to_string());
|
|
26: let query_url = format!("{}/loki/api/v1/query_range", loki_url);
|
|
27:
|
|
28: let resp = client
|
|
29: .get(&query_url)
|
|
30: .query(¶ms)
|
|
31: .send()
|
|
32: .await;
|
|
33:
|
|
34: match resp {
|
|
35: Ok(r) => {
|
|
36: let status = StatusCode::from_u16(r.status().as_u16())
|
|
37: .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
|
|
38: let body = r.bytes().await.unwrap_or_default();
|
|
39: (status, body).into_response()
|
|
40: },
|
|
41: Err(e) => {
|
|
42: tracing::error!("Loki proxy error: {}", e);
|
|
43: (StatusCode::BAD_GATEWAY, e.to_string()).into_response()
|
|
44: }
|
|
45: }
|
|
46: }
|
|
47:
|
|
48: async fn dashboard_handler() -> axum::response::Html<&'static str> {
|
|
49: axum::response::Html(include_str!("../../web/admin.html"))
|
|
50: }
|
|
51:
|
|
52: async fn wait_for_db(db_url: &str) -> PgPool {
|
|
53: loop {
|
|
54: match init_pool(db_url).await {
|
|
55: Ok(pool) => return pool,
|
|
56: Err(e) => {
|
|
57: tracing::warn!("Database not ready yet, retrying in 2s: {}", e);
|
|
58: tokio::time::sleep(Duration::from_secs(2)).await;
|
|
59: }
|
|
60: }
|
|
61: }
|
|
62: }
|
|
63:
|
|
64: async fn log_headers(req: Request, next: Next) -> Response {
|
|
65: tracing::debug!("Request Headers: {:?}", req.headers());
|
|
66: next.run(req).await
|
|
67: }
|
|
68:
|
|
69: pub async fn run() -> anyhow::Result<()> {
|
|
70: let config = Config::new().expect("Failed to load configuration");
|
|
71:
|
|
72: tracing::info!("Starting MadBase Control Plane...");
|
|
73:
|
|
74: let pool = wait_for_db(&config.database_url).await;
|
|
75:
|
|
76: sqlx::migrate!("../migrations")
|
|
77: .run(&pool)
|
|
78: .await
|
|
79: .expect("Failed to run migrations");
|
|
80:
|
|
81: let default_tenant_db_url = std::env::var("DEFAULT_TENANT_DB_URL")
|
|
82: .expect("DEFAULT_TENANT_DB_URL must be set");
|
|
83: let tenant_pool = wait_for_db(&default_tenant_db_url).await;
|
|
84:
|
|
85: let control_state = control_plane::ControlPlaneState {
|
|
86: db: pool.clone(),
|
|
87: tenant_db: tenant_pool.clone(),
|
|
88: };
|
|
89:
|
|
90: let (prometheus_layer, metric_handle) = PrometheusMetricLayer::pair();
|
|
91:
|
|
92: let platform_router = control_plane::router(control_state)
|
|
93: .route("/logs", get(logs_proxy_handler));
|
|
94:
|
|
95: let app = Router::new()
|
|
96: .route("/", get(|| async { "MadBase Control Plane" }))
|
|
97: .route("/health", get(|| async { "OK" }))
|
|
98: .route("/metrics", get(|| async move { metric_handle.render() }))
|
|
99: .route("/dashboard", get(dashboard_handler))
|
|
100: .nest_service("/css", ServeDir::new("web/css"))
|
|
101: .nest_service("/js", ServeDir::new("web/js"))
|
|
102: .nest("/platform/v1", platform_router)
|
|
103: .layer(from_fn(admin_auth_middleware))
|
|
104: .layer(
|
|
105: CorsLayer::new()
|
|
106: .allow_origin(Any)
|
|
107: .allow_methods(Any)
|
|
108: .allow_headers(Any),
|
|
109: )
|
|
110: .layer(TraceLayer::new_for_http())
|
|
111: .layer(from_fn(log_headers))
|
|
112: .layer(prometheus_layer);
|
|
113:
|
|
114: let port = std::env::var("CONTROL_PORT")
|
|
115: .unwrap_or_else(|_| "8001".to_string())
|
|
116: .parse::<u16>()?;
|
|
117:
|
|
118: let addr = SocketAddr::from(([0, 0, 0, 0], port));
|
|
119: tracing::info!("Control plane listening on {}", addr);
|
|
120:
|
|
121: let listener = tokio::net::TcpListener::bind(addr).await?;
|
|
122: axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>()).await?;
|
|
123:
|
|
124: Ok(())
|
|
125: }
|
|
```
|