677 lines
21 KiB
Rust
677 lines
21 KiB
Rust
use crate::api::{ApiError, AuthUser};
|
|
use crate::audit_log;
|
|
use crate::control_plane::{
|
|
AdminNodesResponse, CreateNetworkRequest as CpCreateNetworkRequest,
|
|
CreateTokenRequest as CpCreateTokenRequest, CreateTokenResponse, EnrollmentToken,
|
|
KeyHistoryResponse, KeyPolicyResponse, KeyRotationRequest, KeyRotationResponse,
|
|
KeyRotationPolicy, NetworkInfo,
|
|
};
|
|
use crate::models::{ControlPlane, Network};
|
|
use crate::permissions::{
|
|
ensure_permission_global, PERM_ACL_READ, PERM_ACL_WRITE, PERM_KEY_POLICY_READ,
|
|
PERM_KEY_POLICY_WRITE, PERM_NETWORKS_READ, PERM_NETWORKS_WRITE, PERM_NODES_READ,
|
|
PERM_NODES_WRITE, PERM_TOKENS_WRITE, PERM_AUDIT_READ,
|
|
};
|
|
use axum::extract::{Path, Query, State};
|
|
use axum::http::StatusCode;
|
|
use axum::routing::{get, post};
|
|
use axum::{Json, Router};
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_json::Value;
|
|
use sqlx::PgPool;
|
|
use time::OffsetDateTime;
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Debug, Serialize)]
|
|
struct NetworkSummary {
|
|
id: Uuid,
|
|
control_plane_id: Uuid,
|
|
control_plane_name: String,
|
|
network_id: String,
|
|
name: String,
|
|
dns_domain: Option<String>,
|
|
overlay_v4: Option<String>,
|
|
overlay_v6: Option<String>,
|
|
requires_approval: bool,
|
|
created_at: OffsetDateTime,
|
|
updated_at: OffsetDateTime,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct CreateNetworkRequest {
|
|
control_plane_id: Uuid,
|
|
name: String,
|
|
overlay_v4: Option<String>,
|
|
overlay_v6: Option<String>,
|
|
dns_domain: Option<String>,
|
|
requires_approval: Option<bool>,
|
|
key_rotation_max_age_seconds: Option<u64>,
|
|
bootstrap_token_ttl_seconds: Option<u64>,
|
|
bootstrap_token_uses: Option<u32>,
|
|
bootstrap_token_tags: Option<Vec<String>>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
struct CreateNetworkResult {
|
|
network: NetworkSummary,
|
|
bootstrap_token: Option<EnrollmentToken>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct CreateTokenRequest {
|
|
ttl_seconds: u64,
|
|
uses: u32,
|
|
tags: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct RevokeTokenRequest {
|
|
token_id: String,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct RotateKeysRequest {
|
|
machine_public_key: Option<String>,
|
|
wg_public_key: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct UpdateAclRequest {
|
|
policy: Value,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct NetworkAuditQuery {
|
|
node_id: Option<String>,
|
|
limit: Option<usize>,
|
|
}
|
|
|
|
pub fn router() -> Router<crate::app_state::AppState> {
|
|
Router::new()
|
|
.route("/", get(list_networks).post(create_network))
|
|
.route("/:id", get(get_network).delete(delete_network))
|
|
.route("/:id/tokens", post(create_token))
|
|
.route("/:id/tokens/revoke", post(revoke_token))
|
|
.route("/:id/nodes", get(list_nodes))
|
|
.route("/:id/nodes/:node_id/approve", post(approve_node))
|
|
.route("/:id/nodes/:node_id/revoke", post(revoke_node))
|
|
.route("/:id/nodes/:node_id/rotate-keys", post(rotate_keys))
|
|
.route("/:id/nodes/:node_id/keys", get(node_keys))
|
|
.route("/:id/acl", get(get_acl).put(update_acl))
|
|
.route("/:id/key-policy", get(get_key_policy).put(update_key_policy))
|
|
.route("/:id/audit", get(network_audit))
|
|
}
|
|
|
|
async fn list_networks(
|
|
State(state): State<crate::app_state::AppState>,
|
|
AuthUser { user }: AuthUser,
|
|
) -> Result<Json<Vec<NetworkSummary>>, ApiError> {
|
|
ensure_permission_global(&state.pool, &user, PERM_NETWORKS_READ).await?;
|
|
let rows = sqlx::query_as::<_, NetworkRow>(
|
|
r#"
|
|
SELECT n.id,
|
|
n.control_plane_id,
|
|
n.network_id,
|
|
n.name,
|
|
n.dns_domain,
|
|
n.overlay_v4,
|
|
n.overlay_v6,
|
|
n.requires_approval,
|
|
n.created_at,
|
|
n.updated_at,
|
|
c.name AS control_plane_name
|
|
FROM networks n
|
|
JOIN control_planes c ON c.id = n.control_plane_id
|
|
ORDER BY n.created_at DESC
|
|
"#,
|
|
)
|
|
.fetch_all(&state.pool)
|
|
.await
|
|
.map_err(|_| ApiError::Internal)?;
|
|
|
|
let out = rows
|
|
.into_iter()
|
|
.map(|row| NetworkSummary {
|
|
id: row.id,
|
|
control_plane_id: row.control_plane_id,
|
|
control_plane_name: row.control_plane_name,
|
|
network_id: row.network_id,
|
|
name: row.name,
|
|
dns_domain: row.dns_domain,
|
|
overlay_v4: row.overlay_v4,
|
|
overlay_v6: row.overlay_v6,
|
|
requires_approval: row.requires_approval,
|
|
created_at: row.created_at,
|
|
updated_at: row.updated_at,
|
|
})
|
|
.collect();
|
|
|
|
Ok(Json(out))
|
|
}
|
|
|
|
async fn get_network(
|
|
State(state): State<crate::app_state::AppState>,
|
|
AuthUser { user }: AuthUser,
|
|
Path(id): Path<Uuid>,
|
|
) -> Result<Json<NetworkSummary>, ApiError> {
|
|
ensure_permission_global(&state.pool, &user, PERM_NETWORKS_READ).await?;
|
|
let row = fetch_network_summary(&state.pool, id).await?;
|
|
Ok(Json(NetworkSummary {
|
|
id: row.id,
|
|
control_plane_id: row.control_plane_id,
|
|
control_plane_name: row.control_plane_name,
|
|
network_id: row.network_id,
|
|
name: row.name,
|
|
dns_domain: row.dns_domain,
|
|
overlay_v4: row.overlay_v4,
|
|
overlay_v6: row.overlay_v6,
|
|
requires_approval: row.requires_approval,
|
|
created_at: row.created_at,
|
|
updated_at: row.updated_at,
|
|
}))
|
|
}
|
|
|
|
async fn delete_network(
|
|
State(state): State<crate::app_state::AppState>,
|
|
AuthUser { user }: AuthUser,
|
|
Path(id): Path<Uuid>,
|
|
) -> Result<StatusCode, ApiError> {
|
|
ensure_permission_global(&state.pool, &user, PERM_NETWORKS_WRITE).await?;
|
|
let (network, control_plane) = fetch_network_with_cp(&state.pool, id).await?;
|
|
let client = client_for(&control_plane);
|
|
|
|
// Send delete request to control plane
|
|
client
|
|
.delete_network(&network.network_id)
|
|
.await
|
|
.map_err(|_| ApiError::BadRequest("control plane request failed".to_string()))?;
|
|
|
|
// Delete from database
|
|
sqlx::query("DELETE FROM networks WHERE id = $1")
|
|
.bind(id)
|
|
.execute(&state.pool)
|
|
.await
|
|
.map_err(|_| ApiError::Internal)?;
|
|
|
|
audit_log::record(
|
|
&state.pool,
|
|
Some(user.id),
|
|
"network.delete",
|
|
Some("network"),
|
|
Some(&network.id.to_string()),
|
|
Some(serde_json::json!({
|
|
"network_id": network.network_id,
|
|
"control_plane_id": control_plane.id,
|
|
})),
|
|
)
|
|
.await?;
|
|
|
|
Ok(StatusCode::NO_CONTENT)
|
|
}
|
|
|
|
async fn create_network(
|
|
State(state): State<crate::app_state::AppState>,
|
|
AuthUser { user }: AuthUser,
|
|
Json(req): Json<CreateNetworkRequest>,
|
|
) -> Result<Json<CreateNetworkResult>, ApiError> {
|
|
ensure_permission_global(&state.pool, &user, PERM_NETWORKS_WRITE).await?;
|
|
let control_plane = fetch_control_plane(&state.pool, req.control_plane_id).await?;
|
|
let client = crate::control_plane::ControlPlaneClient::new(
|
|
control_plane.base_url.clone(),
|
|
control_plane.admin_token.clone(),
|
|
);
|
|
|
|
let response = client
|
|
.create_network(CpCreateNetworkRequest {
|
|
name: req.name,
|
|
overlay_v4: req.overlay_v4.clone(),
|
|
overlay_v6: req.overlay_v6.clone(),
|
|
dns_domain: req.dns_domain.clone(),
|
|
requires_approval: req.requires_approval,
|
|
key_rotation_max_age_seconds: req.key_rotation_max_age_seconds,
|
|
bootstrap_token_ttl_seconds: req.bootstrap_token_ttl_seconds,
|
|
bootstrap_token_uses: req.bootstrap_token_uses,
|
|
bootstrap_token_tags: req.bootstrap_token_tags.clone(),
|
|
})
|
|
.await
|
|
.map_err(|_| ApiError::BadRequest("control plane request failed".to_string()))?;
|
|
|
|
let record = insert_network(
|
|
&state.pool,
|
|
&control_plane,
|
|
&response.network,
|
|
req.dns_domain,
|
|
)
|
|
.await?;
|
|
|
|
audit_log::record(
|
|
&state.pool,
|
|
Some(user.id),
|
|
"network.create",
|
|
Some("network"),
|
|
Some(&record.id.to_string()),
|
|
Some(serde_json::json!({
|
|
"network_id": response.network.id,
|
|
"control_plane_id": control_plane.id,
|
|
})),
|
|
)
|
|
.await?;
|
|
|
|
let summary = fetch_network_summary(&state.pool, record.id).await?;
|
|
Ok(Json(CreateNetworkResult {
|
|
network: NetworkSummary {
|
|
id: summary.id,
|
|
control_plane_id: summary.control_plane_id,
|
|
control_plane_name: summary.control_plane_name,
|
|
network_id: summary.network_id,
|
|
name: summary.name,
|
|
dns_domain: summary.dns_domain,
|
|
overlay_v4: summary.overlay_v4,
|
|
overlay_v6: summary.overlay_v6,
|
|
requires_approval: summary.requires_approval,
|
|
created_at: summary.created_at,
|
|
updated_at: summary.updated_at,
|
|
},
|
|
bootstrap_token: response.bootstrap_token,
|
|
}))
|
|
}
|
|
|
|
async fn create_token(
|
|
State(state): State<crate::app_state::AppState>,
|
|
AuthUser { user }: AuthUser,
|
|
Path(id): Path<Uuid>,
|
|
Json(req): Json<CreateTokenRequest>,
|
|
) -> Result<Json<CreateTokenResponse>, ApiError> {
|
|
ensure_permission_global(&state.pool, &user, PERM_TOKENS_WRITE).await?;
|
|
let (network, control_plane) = fetch_network_with_cp(&state.pool, id).await?;
|
|
let client = client_for(&control_plane);
|
|
let response = client
|
|
.create_token(
|
|
&network.network_id,
|
|
CpCreateTokenRequest {
|
|
ttl_seconds: req.ttl_seconds,
|
|
uses: req.uses,
|
|
tags: req.tags,
|
|
owner_user_id: Some(user.id.to_string()),
|
|
owner_email: Some(user.email.clone()),
|
|
owner_is_admin: Some(true),
|
|
},
|
|
)
|
|
.await
|
|
.map_err(|_| ApiError::BadRequest("control plane request failed".to_string()))?;
|
|
|
|
audit_log::record(
|
|
&state.pool,
|
|
Some(user.id),
|
|
"token.create",
|
|
Some("network"),
|
|
Some(&network.id.to_string()),
|
|
None,
|
|
)
|
|
.await?;
|
|
|
|
Ok(Json(response))
|
|
}
|
|
|
|
async fn revoke_token(
|
|
State(state): State<crate::app_state::AppState>,
|
|
AuthUser { user }: AuthUser,
|
|
Path(id): Path<Uuid>,
|
|
Json(req): Json<RevokeTokenRequest>,
|
|
) -> Result<Json<EnrollmentToken>, ApiError> {
|
|
ensure_permission_global(&state.pool, &user, PERM_TOKENS_WRITE).await?;
|
|
let (network, control_plane) = fetch_network_with_cp(&state.pool, id).await?;
|
|
let client = client_for(&control_plane);
|
|
let response = client
|
|
.revoke_token(&req.token_id)
|
|
.await
|
|
.map_err(|_| ApiError::BadRequest("control plane request failed".to_string()))?;
|
|
|
|
audit_log::record(
|
|
&state.pool,
|
|
Some(user.id),
|
|
"token.revoke",
|
|
Some("network"),
|
|
Some(&network.id.to_string()),
|
|
Some(serde_json::json!({ "token_id": req.token_id })),
|
|
)
|
|
.await?;
|
|
|
|
Ok(Json(response))
|
|
}
|
|
|
|
async fn list_nodes(
|
|
State(state): State<crate::app_state::AppState>,
|
|
AuthUser { user }: AuthUser,
|
|
Path(id): Path<Uuid>,
|
|
) -> Result<Json<AdminNodesResponse>, ApiError> {
|
|
ensure_permission_global(&state.pool, &user, PERM_NODES_READ).await?;
|
|
let (network, control_plane) = fetch_network_with_cp(&state.pool, id).await?;
|
|
let client = client_for(&control_plane);
|
|
let response = client
|
|
.list_nodes(&network.network_id)
|
|
.await
|
|
.map_err(|_| ApiError::BadRequest("control plane request failed".to_string()))?;
|
|
Ok(Json(response))
|
|
}
|
|
|
|
async fn approve_node(
|
|
State(state): State<crate::app_state::AppState>,
|
|
AuthUser { user }: AuthUser,
|
|
Path((id, node_id)): Path<(Uuid, String)>,
|
|
) -> Result<Json<crate::control_plane::ApproveNodeResponse>, ApiError> {
|
|
ensure_permission_global(&state.pool, &user, PERM_NODES_WRITE).await?;
|
|
let (_network, control_plane) = fetch_network_with_cp(&state.pool, id).await?;
|
|
let client = client_for(&control_plane);
|
|
let response = client
|
|
.approve_node(&node_id)
|
|
.await
|
|
.map_err(|_| ApiError::BadRequest("control plane request failed".to_string()))?;
|
|
|
|
audit_log::record(
|
|
&state.pool,
|
|
Some(user.id),
|
|
"node.approve",
|
|
Some("node"),
|
|
Some(&node_id),
|
|
None,
|
|
)
|
|
.await?;
|
|
|
|
Ok(Json(response))
|
|
}
|
|
|
|
async fn revoke_node(
|
|
State(state): State<crate::app_state::AppState>,
|
|
AuthUser { user }: AuthUser,
|
|
Path((id, node_id)): Path<(Uuid, String)>,
|
|
) -> Result<Json<crate::control_plane::RevokeNodeResponse>, ApiError> {
|
|
ensure_permission_global(&state.pool, &user, PERM_NODES_WRITE).await?;
|
|
let (_network, control_plane) = fetch_network_with_cp(&state.pool, id).await?;
|
|
let client = client_for(&control_plane);
|
|
let response = client
|
|
.revoke_node(&node_id)
|
|
.await
|
|
.map_err(|_| ApiError::BadRequest("control plane request failed".to_string()))?;
|
|
|
|
audit_log::record(
|
|
&state.pool,
|
|
Some(user.id),
|
|
"node.revoke",
|
|
Some("node"),
|
|
Some(&node_id),
|
|
None,
|
|
)
|
|
.await?;
|
|
|
|
Ok(Json(response))
|
|
}
|
|
|
|
async fn rotate_keys(
|
|
State(state): State<crate::app_state::AppState>,
|
|
AuthUser { user }: AuthUser,
|
|
Path((id, node_id)): Path<(Uuid, String)>,
|
|
Json(req): Json<RotateKeysRequest>,
|
|
) -> Result<Json<KeyRotationResponse>, ApiError> {
|
|
ensure_permission_global(&state.pool, &user, PERM_NODES_WRITE).await?;
|
|
let (_network, control_plane) = fetch_network_with_cp(&state.pool, id).await?;
|
|
let client = client_for(&control_plane);
|
|
let response = client
|
|
.rotate_keys(
|
|
&node_id,
|
|
KeyRotationRequest {
|
|
machine_public_key: req.machine_public_key,
|
|
wg_public_key: req.wg_public_key,
|
|
},
|
|
)
|
|
.await
|
|
.map_err(|_| ApiError::BadRequest("control plane request failed".to_string()))?;
|
|
|
|
audit_log::record(
|
|
&state.pool,
|
|
Some(user.id),
|
|
"node.rotate_keys",
|
|
Some("node"),
|
|
Some(&node_id),
|
|
None,
|
|
)
|
|
.await?;
|
|
|
|
Ok(Json(response))
|
|
}
|
|
|
|
async fn node_keys(
|
|
State(state): State<crate::app_state::AppState>,
|
|
AuthUser { user }: AuthUser,
|
|
Path((id, node_id)): Path<(Uuid, String)>,
|
|
) -> Result<Json<KeyHistoryResponse>, ApiError> {
|
|
ensure_permission_global(&state.pool, &user, PERM_NODES_READ).await?;
|
|
let (_network, control_plane) = fetch_network_with_cp(&state.pool, id).await?;
|
|
let client = client_for(&control_plane);
|
|
let response = client
|
|
.node_keys(&node_id)
|
|
.await
|
|
.map_err(|_| ApiError::BadRequest("control plane request failed".to_string()))?;
|
|
Ok(Json(response))
|
|
}
|
|
|
|
async fn get_acl(
|
|
State(state): State<crate::app_state::AppState>,
|
|
AuthUser { user }: AuthUser,
|
|
Path(id): Path<Uuid>,
|
|
) -> Result<Json<Value>, ApiError> {
|
|
ensure_permission_global(&state.pool, &user, PERM_ACL_READ).await?;
|
|
let (network, control_plane) = fetch_network_with_cp(&state.pool, id).await?;
|
|
let client = client_for(&control_plane);
|
|
let response = client
|
|
.get_acl(&network.network_id)
|
|
.await
|
|
.map_err(|_| ApiError::BadRequest("control plane request failed".to_string()))?;
|
|
Ok(Json(response))
|
|
}
|
|
|
|
async fn update_acl(
|
|
State(state): State<crate::app_state::AppState>,
|
|
AuthUser { user }: AuthUser,
|
|
Path(id): Path<Uuid>,
|
|
Json(req): Json<UpdateAclRequest>,
|
|
) -> Result<Json<Value>, ApiError> {
|
|
ensure_permission_global(&state.pool, &user, PERM_ACL_WRITE).await?;
|
|
let (network, control_plane) = fetch_network_with_cp(&state.pool, id).await?;
|
|
let client = client_for(&control_plane);
|
|
let response = client
|
|
.update_acl(&network.network_id, req.policy)
|
|
.await
|
|
.map_err(|_| ApiError::BadRequest("control plane request failed".to_string()))?;
|
|
|
|
audit_log::record(
|
|
&state.pool,
|
|
Some(user.id),
|
|
"network.update_acl",
|
|
Some("network"),
|
|
Some(&network.id.to_string()),
|
|
None,
|
|
)
|
|
.await?;
|
|
|
|
Ok(Json(response))
|
|
}
|
|
|
|
async fn get_key_policy(
|
|
State(state): State<crate::app_state::AppState>,
|
|
AuthUser { user }: AuthUser,
|
|
Path(id): Path<Uuid>,
|
|
) -> Result<Json<KeyPolicyResponse>, ApiError> {
|
|
ensure_permission_global(&state.pool, &user, PERM_KEY_POLICY_READ).await?;
|
|
let (network, control_plane) = fetch_network_with_cp(&state.pool, id).await?;
|
|
let client = client_for(&control_plane);
|
|
let response = client
|
|
.get_key_policy(&network.network_id)
|
|
.await
|
|
.map_err(|_| ApiError::BadRequest("control plane request failed".to_string()))?;
|
|
Ok(Json(response))
|
|
}
|
|
|
|
async fn update_key_policy(
|
|
State(state): State<crate::app_state::AppState>,
|
|
AuthUser { user }: AuthUser,
|
|
Path(id): Path<Uuid>,
|
|
Json(req): Json<KeyRotationPolicy>,
|
|
) -> Result<Json<KeyPolicyResponse>, ApiError> {
|
|
ensure_permission_global(&state.pool, &user, PERM_KEY_POLICY_WRITE).await?;
|
|
let (network, control_plane) = fetch_network_with_cp(&state.pool, id).await?;
|
|
let client = client_for(&control_plane);
|
|
let response = client
|
|
.update_key_policy(&network.network_id, req)
|
|
.await
|
|
.map_err(|_| ApiError::BadRequest("control plane request failed".to_string()))?;
|
|
|
|
audit_log::record(
|
|
&state.pool,
|
|
Some(user.id),
|
|
"network.update_key_policy",
|
|
Some("network"),
|
|
Some(&network.id.to_string()),
|
|
None,
|
|
)
|
|
.await?;
|
|
|
|
Ok(Json(response))
|
|
}
|
|
|
|
async fn network_audit(
|
|
State(state): State<crate::app_state::AppState>,
|
|
AuthUser { user }: AuthUser,
|
|
Path(id): Path<Uuid>,
|
|
Query(query): Query<NetworkAuditQuery>,
|
|
) -> Result<Json<crate::control_plane::AuditLogResponse>, ApiError> {
|
|
ensure_permission_global(&state.pool, &user, PERM_AUDIT_READ).await?;
|
|
let (network, control_plane) = fetch_network_with_cp(&state.pool, id).await?;
|
|
let client = client_for(&control_plane);
|
|
let response = client
|
|
.audit_log(crate::control_plane::AuditLogQuery {
|
|
network_id: Some(network.network_id),
|
|
node_id: query.node_id,
|
|
limit: query.limit,
|
|
})
|
|
.await
|
|
.map_err(|_| ApiError::BadRequest("control plane request failed".to_string()))?;
|
|
Ok(Json(response))
|
|
}
|
|
|
|
#[derive(sqlx::FromRow)]
|
|
struct NetworkRow {
|
|
id: Uuid,
|
|
control_plane_id: Uuid,
|
|
control_plane_name: String,
|
|
network_id: String,
|
|
name: String,
|
|
dns_domain: Option<String>,
|
|
overlay_v4: Option<String>,
|
|
overlay_v6: Option<String>,
|
|
requires_approval: bool,
|
|
created_at: OffsetDateTime,
|
|
updated_at: OffsetDateTime,
|
|
}
|
|
|
|
async fn fetch_network_summary(pool: &PgPool, id: Uuid) -> Result<NetworkRow, ApiError> {
|
|
sqlx::query_as::<_, NetworkRow>(
|
|
r#"
|
|
SELECT n.id,
|
|
n.control_plane_id,
|
|
n.network_id,
|
|
n.name,
|
|
n.dns_domain,
|
|
n.overlay_v4,
|
|
n.overlay_v6,
|
|
n.requires_approval,
|
|
n.created_at,
|
|
n.updated_at,
|
|
c.name AS control_plane_name
|
|
FROM networks n
|
|
JOIN control_planes c ON c.id = n.control_plane_id
|
|
WHERE n.id = $1
|
|
"#,
|
|
)
|
|
.bind(id)
|
|
.fetch_optional(pool)
|
|
.await
|
|
.map_err(|_| ApiError::Internal)?
|
|
.ok_or(ApiError::NotFound)
|
|
}
|
|
|
|
async fn fetch_network_with_cp(
|
|
pool: &PgPool,
|
|
network_id: Uuid,
|
|
) -> Result<(Network, ControlPlane), ApiError> {
|
|
let network = sqlx::query_as::<_, Network>(
|
|
r#"
|
|
SELECT id, control_plane_id, network_id, name, dns_domain, overlay_v4, overlay_v6,
|
|
requires_approval, created_at, updated_at
|
|
FROM networks
|
|
WHERE id = $1
|
|
"#,
|
|
)
|
|
.bind(network_id)
|
|
.fetch_optional(pool)
|
|
.await
|
|
.map_err(|_| ApiError::Internal)?
|
|
.ok_or(ApiError::NotFound)?;
|
|
|
|
let control_plane = fetch_control_plane(pool, network.control_plane_id).await?;
|
|
Ok((network, control_plane))
|
|
}
|
|
|
|
async fn fetch_control_plane(pool: &PgPool, id: Uuid) -> Result<ControlPlane, ApiError> {
|
|
sqlx::query_as::<_, ControlPlane>(
|
|
r#"
|
|
SELECT id, name, base_url, admin_token, region, created_at, updated_at
|
|
FROM control_planes
|
|
WHERE id = $1
|
|
"#,
|
|
)
|
|
.bind(id)
|
|
.fetch_optional(pool)
|
|
.await
|
|
.map_err(|_| ApiError::Internal)?
|
|
.ok_or(ApiError::NotFound)
|
|
}
|
|
|
|
async fn insert_network(
|
|
pool: &PgPool,
|
|
control_plane: &ControlPlane,
|
|
network: &NetworkInfo,
|
|
dns_override: Option<String>,
|
|
) -> Result<Network, ApiError> {
|
|
let now = OffsetDateTime::now_utc();
|
|
sqlx::query_as::<_, Network>(
|
|
r#"
|
|
INSERT INTO networks (
|
|
id, control_plane_id, network_id, name, dns_domain, overlay_v4, overlay_v6,
|
|
requires_approval, created_at, updated_at
|
|
)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $9)
|
|
RETURNING id, control_plane_id, network_id, name, dns_domain, overlay_v4, overlay_v6,
|
|
requires_approval, created_at, updated_at
|
|
"#,
|
|
)
|
|
.bind(Uuid::new_v4())
|
|
.bind(control_plane.id)
|
|
.bind(&network.id)
|
|
.bind(&network.name)
|
|
.bind(dns_override.or_else(|| Some(network.dns_domain.clone())))
|
|
.bind(&network.overlay_v4)
|
|
.bind(&network.overlay_v6)
|
|
.bind(network.requires_approval)
|
|
.bind(now)
|
|
.fetch_one(pool)
|
|
.await
|
|
.map_err(|_| ApiError::Internal)
|
|
}
|
|
|
|
fn client_for(control_plane: &ControlPlane) -> crate::control_plane::ControlPlaneClient {
|
|
crate::control_plane::ControlPlaneClient::new(
|
|
control_plane.base_url.clone(),
|
|
control_plane.admin_token.clone(),
|
|
)
|
|
}
|