Skip to main content

dbctl_core/docker/
engine.rs

1use bollard::Docker;
2use bollard::container::Config as ContainerConfig;
3use bollard::container::CreateContainerOptions;
4use bollard::container::StartContainerOptions;
5use bollard::container::Stats;
6use bollard::image::CreateImageOptions;
7use bollard::models::*;
8use futures_util::stream::TryStreamExt;
9use std::collections::HashMap;
10use std::default::Default;
11
12use crate::db::Database;
13
14pub struct DockerEngine {
15    pub docker: Docker,
16}
17
18impl DockerEngine {
19    pub async fn new() -> Self {
20        let docker = Docker::connect_with_local_defaults().unwrap();
21        DockerEngine { docker }
22    }
23
24    pub async fn start_container<D: Database>(&self, db: D) -> anyhow::Result<String> {
25        self.docker
26            .create_image(
27                Some(CreateImageOptions {
28                    from_image: db.image(),
29                    ..Default::default()
30                }),
31                None,
32                None,
33            )
34            .try_collect::<Vec<_>>()
35            .await?;
36
37        let env_vars: Vec<String> = db
38            .env_vars()
39            .into_iter()
40            .map(|(k, v)| format!("{}={}", k, v))
41            .collect();
42        let env: Vec<&str> = env_vars.iter().map(|s| s.as_str()).collect();
43
44        let port_str = format!("{}/tcp", db.port());
45
46        let config = ContainerConfig {
47            image: Some(db.image()),
48            env: Some(env),
49            exposed_ports: Some({
50                let mut map = HashMap::new();
51                map.insert(port_str.as_str(), HashMap::new());
52                map
53            }),
54            host_config: Some(HostConfig {
55                port_bindings: Some({
56                    let mut pb = HashMap::new();
57                    pb.insert(
58                        port_str.clone(),
59                        Some(vec![PortBinding {
60                            host_ip: Some("0.0.0.0".to_string()),
61                            host_port: Some(db.port().to_string()),
62                        }]),
63                    );
64                    pb
65                }),
66                ..Default::default()
67            }),
68            ..Default::default()
69        };
70
71        let container_name = format!("dbctl-{}", db.name());
72
73        let created = self
74            .docker
75            .create_container(
76                Some(CreateContainerOptions::<&str> {
77                    name: &container_name,
78
79                    platform: None,
80                }),
81                config,
82            )
83            .await?;
84
85        self.docker
86            .start_container(&created.id, None::<StartContainerOptions<String>>)
87            .await?;
88
89        Ok(created.id)
90    }
91
92    pub async fn stop_container(&self, container_id: &str) -> anyhow::Result<()> {
93        self.docker.stop_container(container_id, None).await?;
94
95        self.docker.remove_container(container_id, None).await?;
96
97        Ok(())
98    }
99
100    pub async fn container_logs(&self, container_id: &str) -> anyhow::Result<Vec<String>> {
101        let logs = self
102            .docker
103            .logs(
104                container_id,
105                Some(bollard::container::LogsOptions {
106                    stdout: true,
107                    stderr: true,
108                    follow: false,
109                    tail: "100".to_string(),
110                    ..Default::default()
111                }),
112            )
113            .try_collect::<Vec<_>>()
114            .await?;
115
116        let log_lines = logs.iter().map(|log| log.to_string()).collect();
117
118        Ok(log_lines)
119    }
120
121    pub async fn container_stats(
122        &self,
123        container_id: &str,
124    ) -> anyhow::Result<Stats> {
125        let stats = self
126            .docker
127            .stats(
128                container_id,
129                Some(bollard::container::StatsOptions {
130                    stream: false,
131                    one_shot: true,
132                }),
133            )
134            .try_collect::<Vec<_>>()
135            .await?;
136
137        if let Some(container_stats) = stats.first() {
138            Ok(container_stats.clone())
139        } else {
140            anyhow::bail!("No stats available for container {}", container_id)
141        }
142    }
143
144    pub async fn container_info(
145        &self,
146        container_id: &str,
147    ) -> anyhow::Result<ContainerInspectResponse> {
148        let info = self.docker.inspect_container(container_id, None).await?;
149        Ok(info)
150    }
151}