jinx_proxy/
docker.rs

1use bollard::container::{Config, CreateContainerOptions};
2use bollard::image::BuildImageOptions;
3use bollard::models::{HostConfig, PortBinding};
4use bollard::network::CreateNetworkOptions;
5use bollard::service::{
6    EndpointPortConfig, EndpointSpec, Mount, MountTypeEnum, NetworkAttachmentConfig, ServiceSpec,
7    ServiceSpecMode, ServiceSpecModeReplicated, TaskSpec, TaskSpecContainerSpec,
8    TaskSpecContainerSpecFile, TaskSpecContainerSpecSecrets,
9};
10use bollard::Docker;
11use futures_util::stream::StreamExt;
12use std::collections::HashMap;
13use std::env;
14use std::fs::File;
15use std::io::BufRead;
16use std::io::BufReader;
17
18use super::log_exit;
19use crate::service::JinxService;
20
21// builds the provided tar.gz file with meta from the JinxService
22pub async fn build_docker_image(client: Docker, jinx_service: &JinxService, bytes: Vec<u8>) {
23    // define image options
24    let config = BuildImageOptions {
25        dockerfile: "Dockerfile",
26        t: &jinx_service.image_name,
27        ..Default::default()
28    };
29
30    let mut image_build_stream = client.build_image(config, None, Some(bytes.into()));
31
32    while let Some(msg) = image_build_stream.next().await {
33        let message = match msg {
34            Ok(msg) => msg,
35            Err(err) => log_exit!("[DOCKER] Failed to get build message", err),
36        };
37        let stream = match message.stream {
38            Some(stream) => stream,
39            None => "".to_string(),
40        };
41
42        print!("{}", stream);
43    }
44}
45
46// creates a docker network
47pub async fn create_jinx_network(client: Docker) {
48    // define jinx network
49    let config = CreateNetworkOptions {
50        name: "jinx_network",
51        check_duplicate: true,
52        driver: "overlay",
53        internal: false,
54        ..Default::default()
55    };
56
57    let network_id = match client.create_network(config).await {
58        Ok(id) => id,
59        Err(err) => log_exit!("[DOCKER] Failed to create jinx_network", err),
60    };
61
62    println!("Jinx network created: {:?}", network_id);
63}
64
65// returns a Docker client
66pub fn get_client() -> Docker {
67    let docker = match Docker::connect_with_socket_defaults() {
68        Ok(docker) => docker,
69        Err(err) => log_exit!("[DOCKER] Failed to connect to docker socket", err),
70    };
71
72    docker
73}
74
75// returns a vector of lines from the .dockerignore file
76pub fn get_dockerignore() -> Vec<String> {
77    let mut lines = vec![];
78
79    // get current directory
80    let current_dir = env::current_dir().expect("[DOCKER] Failed to get current directory");
81
82    // attempt to open .dockerignore in current directory
83    let jinx_path = format!("{}/.dockerignore", current_dir.display());
84    let file = match File::open(jinx_path) {
85        Err(_err) => return lines,
86        Ok(file) => file,
87    };
88
89    // read the file
90    let reader = BufReader::new(file);
91
92    // add lines to array
93    for line in reader.lines() {
94        let ln = match line {
95            Err(err) => format!("Error: {}", err),
96            Ok(line) => line,
97        };
98        lines.push(ln);
99    }
100
101    lines
102}
103
104// creates a docker service
105pub async fn create_service(client: Docker, jinx_service: &JinxService) {
106    // create service name with jinx tag
107    let name = format!("{}{}", &jinx_service.name, "-jinx".to_string());
108
109    _create_service(client, jinx_service, name).await;
110}
111
112// creates a jinx proxy service
113pub async fn create_jinx_proxy_service(client: Docker, jinx_service: &JinxService) {
114    // create jinx proxy service
115    let name = "jinx-proxy".to_string();
116
117    _create_service(client, jinx_service, name).await;
118}
119
120// runs an image
121pub async fn run_image(
122    client: Docker,
123    image_name: &str,
124    ports: Vec<&str>,
125    vols: Vec<&str>,
126    envs: Option<Vec<&str>>,
127    cmds: Option<Vec<&str>>,
128) {
129    let name = image_name.replace("/", "_");
130    let options = Some(CreateContainerOptions {
131        name: format!("jinx-{}", name),
132    });
133
134    let mut volumes: HashMap<&str, HashMap<(), ()>> = HashMap::new();
135
136    let mut port_bindings = HashMap::new();
137    for port in ports {
138        let split: Vec<&str> = port.split(':').collect();
139        let p = vec![PortBinding {
140            host_ip: None,
141            host_port: Some(split[0].to_string()),
142        }];
143
144        port_bindings.insert(split[1].to_string(), Some(p));
145    }
146
147    let host_config = HostConfig {
148        port_bindings: Some(port_bindings),
149        ..Default::default()
150    };
151
152    for vol in vols {
153        volumes.insert(vol, HashMap::new());
154    }
155
156    let config = Config {
157        image: Some(image_name),
158        volumes: Some(volumes),
159        cmd: cmds,
160        env: envs,
161        host_config: Some(host_config),
162        ..Default::default()
163    };
164
165    let container_id = match client.create_container(options, config).await {
166        Ok(id) => id,
167        Err(err) => log_exit!("[DOCKER] Failed to create image", err),
168    };
169
170    println!("Created container: {:?}", container_id.id);
171
172    match client
173        .start_container::<String>(&container_id.id, None)
174        .await
175    {
176        Ok(none) => none,
177        Err(err) => log_exit!("[DOCKER] Failed to start image", err),
178    };
179
180    println!("Started container: {:?}", container_id.id);
181}
182
183async fn _create_service(client: Docker, jinx_service: &JinxService, name: String) {
184    // define network to attach service
185    let networks = vec![NetworkAttachmentConfig {
186        target: Some("jinx_network".to_string()),
187        ..Default::default()
188    }];
189
190    // define service ports
191    let mut ports = vec![];
192    if name.contains("jinx-proxy") {
193        ports.push(EndpointPortConfig {
194            target_port: Some(80),
195            published_port: Some(80),
196            ..Default::default()
197        });
198        ports.push(EndpointPortConfig {
199            target_port: Some(443),
200            published_port: Some(443),
201            ..Default::default()
202        });
203    } else {
204        ports.push(EndpointPortConfig {
205            target_port: Some(jinx_service.image_port),
206            published_port: jinx_service.published_port,
207            ..Default::default()
208        });
209    }
210
211    let endpoint_spec = EndpointSpec {
212        ports: Some(ports),
213        ..Default::default()
214    };
215
216    // define mounts
217    let mut mounts = vec![];
218    if jinx_service.image_volumes.is_some() {
219        let image_volumes = jinx_service.image_volumes.clone().unwrap();
220
221        for mount in image_volumes.iter() {
222            let split: Vec<&str> = mount.split(':').collect();
223            let m = Mount {
224                source: Some(split[0].to_string()),
225                target: Some(split[1].to_string()),
226                typ: Some(MountTypeEnum::BIND),
227                ..Default::default()
228            };
229            mounts.push(m);
230        }
231    }
232
233    // define envs
234    let mut envs = vec![];
235    if jinx_service.image_envs.is_some() {
236        envs = jinx_service.image_envs.clone().unwrap();
237    }
238
239    // define secrets
240    let mut secrets = vec![];
241    if jinx_service.image_secrets.is_some() {
242        let image_secrets = jinx_service.image_secrets.clone().unwrap();
243        for secret in image_secrets.iter() {
244            let split: Vec<&str> = secret.split(':').collect();
245            let s = TaskSpecContainerSpecSecrets {
246                secret_name: Some(split[0].to_string()),
247                secret_id: Some(split[1].to_string()),
248                file: Some(TaskSpecContainerSpecFile {
249                    name: Some(split[0].to_string()),
250                    uid: Some("0".to_string()),
251                    gid: Some("0".to_string()),
252                    mode: Some(292),
253                }),
254            };
255            secrets.push(s);
256        }
257    }
258
259    // define service
260    let service = ServiceSpec {
261        name: Some(name),
262        mode: Some(ServiceSpecMode {
263            replicated: Some(ServiceSpecModeReplicated { replicas: Some(1) }),
264            ..Default::default()
265        }),
266        task_template: Some(TaskSpec {
267            container_spec: Some(TaskSpecContainerSpec {
268                image: Some(jinx_service.image_name.to_string()),
269                mounts: Some(mounts),
270                env: Some(envs.clone()),
271                secrets: Some(secrets),
272                ..Default::default()
273            }),
274            ..Default::default()
275        }),
276        networks: Some(networks),
277        endpoint_spec: Some(endpoint_spec),
278        ..Default::default()
279    };
280
281    let service = match client.create_service(service, None).await {
282        Ok(svc) => svc,
283        Err(err) => log_exit!("[DOCKER] Failed to create jinx service", err),
284    };
285    let service_id = match service.id {
286        Some(id) => id,
287        None => "".to_string(),
288    };
289
290    println!("Jinx service created: {}", service_id);
291}