Skip to content

Commit a8d1e2d

Browse files
committed
FEAT: list, get, start, restart, stop docker container
1 parent 08f8eb7 commit a8d1e2d

9 files changed

Lines changed: 259 additions & 673 deletions

File tree

Cargo.lock

Lines changed: 104 additions & 639 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ version = "0.1.0"
44
edition = "2024"
55

66
[dependencies]
7-
actix-web = "4.9.0"
7+
axum = "0.8.1"
88
bollard = "0.18.1"
99
tokio = { version = "1.43.0", features = ["full"] }
1010
serde = { version = "1.0.217", features = ["derive"] }
1111
serde_json = "1.0.138"
12-
actix-cors = "0.7.0"

src/controllers/docker.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use axum::{extract::Path, http::StatusCode, response::IntoResponse, Json};
2+
use serde_json::json;
3+
use bollard::Docker;
4+
5+
use crate::errors::error_status::ErrorStatus;
6+
7+
fn get_docker_client() -> Result<Docker, ErrorStatus> {
8+
Docker::connect_with_local_defaults()
9+
.map_err(|_| ErrorStatus::new(StatusCode::INTERNAL_SERVER_ERROR, format!("Error connecting to Docker")))
10+
}
11+
12+
pub async fn get_containers() -> impl IntoResponse {
13+
let options = Some(bollard::container::ListContainersOptions::<String> {
14+
all: true,
15+
..Default::default()
16+
});
17+
18+
let docker = get_docker_client();
19+
20+
let container = match docker {
21+
Ok(docker) => {
22+
match docker.list_containers(options).await {
23+
Ok(containers) => containers,
24+
Err(_) => return ErrorStatus::new(StatusCode::INTERNAL_SERVER_ERROR, "Error listing containers".to_string()).into_response()
25+
}
26+
},
27+
Err(error) => return error.into_response()
28+
};
29+
30+
let response = json!({"containers": container});
31+
(StatusCode::OK, Json(response)).into_response()
32+
}
33+
34+
pub async fn get_container(Path(id): Path<String>) -> impl IntoResponse {
35+
let docker = get_docker_client();
36+
37+
let container = match docker {
38+
Ok(docker) => {
39+
match docker.inspect_container(&id, None).await {
40+
Ok(container) => container,
41+
Err(_) => return ErrorStatus::new(StatusCode::INTERNAL_SERVER_ERROR, "Error inspecting container".to_string()).into_response()
42+
}
43+
},
44+
Err(error) => return error.into_response()
45+
};
46+
47+
let response = json!({"container": container});
48+
(StatusCode::OK, Json(response)).into_response()
49+
}
50+
51+
pub async fn start_container(Path(id): Path<String>) -> impl IntoResponse {
52+
let docker = get_docker_client();
53+
54+
match docker {
55+
Ok(docker) => {
56+
match docker.start_container(&id, None::<bollard::container::StartContainerOptions<String>>).await {
57+
Ok(_) => (StatusCode::OK, Json(json!({"message": "Container started"}))).into_response(),
58+
Err(err) => {
59+
println!("Error starting container {}", err);
60+
ErrorStatus::new(StatusCode::INTERNAL_SERVER_ERROR, "Error starting container".to_string())
61+
}.into_response()
62+
}
63+
},
64+
Err(error) => error.into_response()
65+
}
66+
}
67+
68+
pub async fn stop_container(Path(id): Path<String>) -> impl IntoResponse {
69+
let docker = get_docker_client();
70+
71+
match docker {
72+
Ok(docker) => {
73+
match docker.stop_container(&id, None::<bollard::container::StopContainerOptions>).await {
74+
Ok(_) => (StatusCode::OK, Json(json!({"message": "Container stopped"}))).into_response(),
75+
Err(_) => ErrorStatus::new(StatusCode::INTERNAL_SERVER_ERROR, "Error stopping container".to_string()).into_response()
76+
}
77+
},
78+
Err(error) => error.into_response()
79+
}
80+
}
81+
82+
pub async fn restart_container(Path(id): Path<String>) -> impl IntoResponse {
83+
let docker = get_docker_client();
84+
85+
match docker {
86+
Ok(docker) => {
87+
match docker.restart_container(&id, None::<bollard::container::RestartContainerOptions>).await {
88+
Ok(_) => (StatusCode::OK, Json(json!({"message": "Container restarted"}))).into_response(),
89+
Err(_) => ErrorStatus::new(StatusCode::INTERNAL_SERVER_ERROR, "Error restarting container".to_string()).into_response()
90+
}
91+
},
92+
Err(error) => error.into_response()
93+
}
94+
}

src/controllers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod docker;

src/errors/error_status.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use std::fmt;
2+
3+
use axum::{body::Body, http::StatusCode, response::{IntoResponse, Response}, Json};
4+
use serde_json::json;
5+
6+
#[derive(Debug)]
7+
pub struct ErrorStatus {
8+
pub status: StatusCode,
9+
pub message: String,
10+
}
11+
12+
impl ErrorStatus {
13+
pub fn new(status: StatusCode, message: String) -> Self {
14+
Self { status, message }
15+
}
16+
}
17+
18+
impl fmt::Display for ErrorStatus {
19+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20+
write!(f, "Error {}: {}", self.status.as_str(), self.message)
21+
}
22+
}
23+
24+
impl IntoResponse for ErrorStatus {
25+
fn into_response(self) -> Response<Body> {
26+
let body = json!({"error": self.message});
27+
(self.status, Json(body)).into_response()
28+
}
29+
}

src/errors/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod error_status;

src/main.rs

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,13 @@
1-
use std::sync::Arc;
1+
mod routes;
2+
mod controllers;
3+
mod errors;
24

3-
use actix_cors::Cors;
4-
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
5-
use bollard::Docker;
5+
use routes::setup_routes;
66

7-
async fn list_containers(docker: web::Data<Arc<Docker>>) -> impl Responder {
8-
match docker.list_containers::<String>(None).await {
9-
Ok(containers) => HttpResponse::Ok().json(containers),
10-
Err(e) => HttpResponse::InternalServerError().body(format!("Error listing containers: {}", e)),
11-
}
12-
}
13-
14-
#[actix_web::main]
15-
async fn main() -> std::io::Result<()> {
16-
let docker = Docker::connect_with_local_defaults().unwrap();
17-
let docker = Arc::new(docker);
7+
#[tokio::main]
8+
async fn main() {
9+
let app = setup_routes();
1810

19-
HttpServer::new(move || {
20-
App::new()
21-
.wrap(Cors::default()
22-
.allow_any_origin()
23-
.allow_any_method()
24-
.allow_any_header()
25-
.max_age(3600)
26-
.send_wildcard())
27-
.wrap(actix_web::middleware::Logger::default())
28-
.wrap(actix_web::middleware::NormalizePath::default())
29-
.wrap(actix_web::middleware::Compress::default())
30-
.app_data(web::Data::new(docker.clone()))
31-
.route("/containers", web::get().to(list_containers))
32-
})
33-
.bind("0.0.0.0:8181")?
34-
.run()
35-
.await
11+
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
12+
axum::serve(listener, app).await.unwrap();
3613
}

src/routes/docker.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use axum::{Router, routing::get};
2+
3+
use crate::controllers::docker;
4+
5+
pub fn create_routes() -> Router {
6+
Router::new()
7+
.route("/", get(docker::get_containers))
8+
.route("/{id}", get(docker::get_container))
9+
.route("/start/{id}", get(docker::start_container))
10+
.route("/stop/{id}", get(docker::stop_container))
11+
.route("/restart/{id}", get(docker::restart_container))
12+
}

src/routes/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use axum::Router;
2+
3+
mod docker;
4+
5+
pub fn setup_routes() -> Router {
6+
Router::new()
7+
.nest("/docker", docker::create_routes())
8+
}

0 commit comments

Comments
 (0)