Skip to main content

xray_docker/api/
mod.rs

1use std::env::{self};
2
3mod chunk_processor;
4mod connection;
5mod docker_host;
6mod util;
7
8use connection::DockerApiConnection;
9use docker_host::{ContextMetadata, DEFAULT_DOCKER_HOST, DOCKER_HOST_ENV_VAR, DockerConfig, DockerHost};
10use http::StatusCode;
11use util::encode_request;
12
13use crate::{DockerError, Result};
14
15pub struct DockerApi {
16    connection: DockerApiConnection,
17    buffer: Vec<u8>,
18}
19
20impl DockerApi {
21    /// Creates a new [DockerApi] instance using the Docker Host resolution logic from [Self::get_docker_host].
22    pub fn new_with_host_resolution() -> Result<Self> {
23        let host = Self::get_docker_host()?;
24        let connection = DockerApiConnection::connect(host)?;
25
26        Ok(DockerApi {
27            connection,
28            buffer: Vec::new(),
29        })
30    }
31
32    /// Checks the provided image exists locally.
33    pub fn image_is_present(&mut self, image: &str) -> Result<bool> {
34        let request = http::Request::builder()
35            .uri(format!("/images/{image}/json"))
36            .header("host", "docker")
37            .header("accept", "*/*")
38            .body(Vec::new())
39            .map_err(|_| DockerError::Other("failed to construct the image inspect request".into()))?;
40
41        // Send the  request and receive a response
42        self.buffer.clear();
43        encode_request(&request, &mut self.buffer)?;
44        let status_code = self.connection.make_request(&mut self.buffer)?;
45
46        Ok(status_code == StatusCode::OK)
47    }
48
49    /// Pulls the provided image.
50    pub fn pull_image(&mut self, image: &str) -> Result<()> {
51        let tag = image.split_once(":").map(|(_, tag)| tag).unwrap_or("latest");
52        let request = http::Request::builder()
53            .uri(format!("/images/create?fromImage={image}&tag={tag}"))
54            .method("POST")
55            .header("host", "docker")
56            .header("accept", "*/*")
57            .body(Vec::new())
58            .map_err(|_| {
59                DockerError::Other(format!("failed to construct the request to pull the '{image}' image").into())
60            })?;
61
62        // Send the  request and receive a response
63        self.buffer.clear();
64        encode_request(&request, &mut self.buffer)?;
65        let status_code = self.connection.make_request(&mut self.buffer)?;
66
67        if status_code != http::StatusCode::OK {
68            match status_code {
69                StatusCode::NOT_FOUND => Err(DockerError::Other("failed to pull the image: no such image".into())),
70                _ => Err(DockerError::Other("failed to pull the image".into())),
71            }
72        } else {
73            Ok(())
74        }
75    }
76
77    /// Downloads a tarball of the provided image.
78    pub fn export_image(&mut self, image: &str) -> Result<&[u8]> {
79        let request = http::Request::builder()
80            .uri(format!("/images/{image}/get"))
81            .header("host", "docker")
82            .header("accept", "*/*")
83            .body(Vec::new())
84            .map_err(|_| {
85                DockerError::Other(format!("failed to construct the request to export the '{image}' image").into())
86            })?;
87
88        // Send the  request and receive a response
89        self.buffer.clear();
90        encode_request(&request, &mut self.buffer)?;
91        let status_code = self.connection.make_request(&mut self.buffer)?;
92
93        if status_code != http::StatusCode::OK {
94            match status_code {
95                StatusCode::NOT_FOUND => Err(DockerError::Other("failed to export the image: no such image".into())),
96                _ => Err(DockerError::Other("failed to export the image".into())),
97            }
98        } else {
99            Ok(&self.buffer)
100        }
101    }
102
103    /// Consumets this Docker API instance and returns the underlying buffer.
104    pub fn into_buffer(self) -> Vec<u8> {
105        self.buffer
106    }
107
108    /// Returns the resolved Docker host for the current system.
109    pub fn get_docker_host() -> Result<DockerHost> {
110        if let Ok(host) = env::var(DOCKER_HOST_ENV_VAR) {
111            // No need to check anything else if we have an explicit env var
112            return Ok(host.into());
113        }
114
115        // If we don't have an env, we need to check the Docker Context
116        let Some(docker_config) = DockerConfig::new()? else {
117            // We can't do anything else at this point besides returning the default Docker host
118            return Ok(DEFAULT_DOCKER_HOST.into());
119        };
120
121        let context_meta = ContextMetadata::new_from_docker_config(&docker_config)?;
122        if let Some(context_meta) = context_meta {
123            Ok(context_meta.into_docker_host().into())
124        } else {
125            // Return default Docker host for the current OS
126            Ok(DEFAULT_DOCKER_HOST.into())
127        }
128    }
129}