aboutsummaryrefslogtreecommitdiffstats
path: root/zoned/src/http.rs
blob: 5149b5d7438800b429bb69440cfd73c3121a8a14 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
use axum::{
    extract::{ws::WebSocketUpgrade, Extension, Query, TypedHeader},
    headers,
    response::IntoResponse,
    routing::{get, post},
    Json, Router,
};
use std::{process::Command, sync::Arc};
use tracing::{info, instrument, warn};
use zone_core::{
    CloneOptions, Container, ContainerOptions, Status, FilterContainer, Runtime,
    Templates, Volumes,
};

use crate::{error::CommandError, ws, Result, State};

#[instrument()]
pub fn build_routes() -> Router {
    Router::new()
        .route("/test", get(test_endpoint))
        .route("/container", post(clone_container))
        .route("/container/list?<container..>", get(container_list))
        .route("/container/attach", get(ws_upgrade))
}

/// # Test endpoint
///
/// Returns a list of containers based on the query.
#[instrument(ret, skip(_state))]
async fn test_endpoint(Extension(_state): Extension<Arc<State>>) -> Json<String> {
    Json("All good!".into())
}

/// List containers
///
/// Returns a list of containers based on the query.
#[instrument(err, ret)]
async fn container_list(
    Query(params): Query<ContainerOptions>,
    Extension(state): Extension<Arc<State>>,
) -> Result<Json<Vec<Container>>> {
    let containers = state
        .nspawn
        .list()?
        .into_iter()
        .filter_container(&params)
        .into();

    Ok(containers)
}

/// Create container
///
/// Creates a new container volume from the provided container json data
#[instrument(err, ret, skip(state))]
async fn clone_container(
    Json(clone_options): Json<CloneOptions>,
    Extension(state): Extension<Arc<State>>,
) -> Result<Json<(Container, Status)>> {
    let container = state
        .zfs
        .clone_from(clone_options.template, clone_options.owner)?;

    let volume = state.zfs.get_volume(&container)?;

    if let Some(script) = &state.config.init_script {
        Command::new(script)
            .env("DIRECTORY", &volume)
            .env("USER", &container.owner)
            .env("TEMPLATE", &container.template)
            .output()
            .map_err(CommandError::from)?
            .status
            .code()
            .map_or(Ok(()), |c| Err(CommandError::from(c)))?;
    };

    state.nspawn.create(volume, &container)?;

    Ok(Json::from((container, Status::Running)))
}

/// Upgrade to websocket
///
/// Request to upgrade to a websocket connection
#[instrument(ret, skip_all)]
async fn ws_upgrade(
    ws: WebSocketUpgrade,
    user_agent: Option<TypedHeader<headers::UserAgent>>,
    Extension(state): Extension<Arc<State>>,
) -> impl IntoResponse {
    let agent = user_agent.map_or("Unknown".to_string(), |u| u.to_string());
    info!(%agent, "Client connected");

    ws.on_upgrade(|socket| ws::handler(socket, state))
}

#[cfg(test)]
mod tests {
    #[test]
    fn hello_world() {
        // use super::*;
        assert!("true" == "true");
    }
}