dbctl_core/docker/
engine.rs1use 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}