docker_client_async/
volume.rs

1/*
2 * Copyright 2020 Damian Peckett <damian@pecke.tt>.
3 * Copyright 2013-2018 Docker, Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18//! Docker volume api implementation.
19
20use crate::error::*;
21use crate::types::client::{VolumeCreateBody, VolumeListOKBody, VolumesPruneReport};
22use crate::types::filters::Args;
23use crate::types::volume::Volume;
24use crate::{read_response_body, version, DockerEngineClient};
25use hyper::client::connect::Connect;
26use hyper::{Body, Method, Request};
27use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
28use snafu::{ensure, ResultExt};
29use std::collections::HashMap;
30use tokio::time::timeout;
31
32impl<C: Connect + Clone + Send + Sync + 'static> DockerEngineClient<C> {
33    /// volume_list returns the volumes configured in the docker host.
34    pub async fn volume_list(&self, filter: Option<Args>) -> Result<VolumeListOKBody, Error> {
35        let mut query_params: HashMap<String, String> = HashMap::new();
36        if let Some(filter) = filter {
37            query_params.insert(
38                "filters".into(),
39                serde_json::to_string(&filter.fields).context(JsonSerializationError {})?,
40            );
41        }
42        let query_params = if !query_params.is_empty() {
43            Some(query_params)
44        } else {
45            None
46        };
47
48        let request = Request::builder()
49            .method(Method::GET)
50            .uri(self.request_uri("/volumes", query_params)?)
51            .header("Accept", "application/json")
52            .body(Body::empty())
53            .context(HttpClientRequestBuilderError {})?;
54
55        let client = self.client.as_ref().unwrap();
56        let response = timeout(self.timeout, client.request(request))
57            .await
58            .context(HttpClientTimeoutError {})?
59            .context(HttpClientError {})?;
60        ensure!(
61            response.status().is_success(),
62            HttpClientResponseError {
63                status: response.status().as_u16()
64            }
65        );
66        let response_body = read_response_body(response, self.timeout).await?;
67        Ok(serde_json::from_str(&response_body).context(JsonDeserializationError {})?)
68    }
69
70    /// volume_create creates a volume in the docker host.
71    pub async fn volume_create(&self, options: VolumeCreateBody) -> Result<Volume, Error> {
72        let request = Request::builder()
73            .method(Method::POST)
74            .uri(self.request_uri("/volumes/create", None)?)
75            .header("Content-Type", "application/json")
76            .header("Accept", "application/json")
77            .body(Body::from(
78                serde_json::to_string(&options).context(JsonSerializationError {})?,
79            ))
80            .context(HttpClientRequestBuilderError {})?;
81
82        let client = self.client.as_ref().unwrap();
83        let response = timeout(self.timeout, client.request(request))
84            .await
85            .context(HttpClientTimeoutError {})?
86            .context(HttpClientError {})?;
87        ensure!(
88            response.status().is_success(),
89            HttpClientResponseError {
90                status: response.status().as_u16()
91            }
92        );
93        let response_body = read_response_body(response, self.timeout).await?;
94        Ok(serde_json::from_str(&response_body).context(JsonDeserializationError {})?)
95    }
96
97    /// volume_inspect returns the information about a specific volume in the docker host.
98    pub async fn volume_inspect(&self, volume_id: &str) -> Result<Volume, Error> {
99        let request = Request::builder()
100            .method(Method::GET)
101            .uri(self.request_uri(
102                &format!(
103                    "/volumes/{}",
104                    utf8_percent_encode(volume_id, NON_ALPHANUMERIC).to_string()
105                ),
106                None,
107            )?)
108            .header("Accept", "application/json")
109            .body(Body::empty())
110            .context(HttpClientRequestBuilderError {})?;
111
112        let client = self.client.as_ref().unwrap();
113        let response = timeout(self.timeout, client.request(request))
114            .await
115            .context(HttpClientTimeoutError {})?
116            .context(HttpClientError {})?;
117        ensure!(
118            response.status().is_success(),
119            HttpClientResponseError {
120                status: response.status().as_u16()
121            }
122        );
123        let response_body = read_response_body(response, self.timeout).await?;
124        Ok(serde_json::from_str(&response_body).context(JsonDeserializationError {})?)
125    }
126
127    /// volume_remove removes a volume from the docker host.
128    pub async fn volume_remove(&self, volume_id: &str, force: bool) -> Result<(), Error> {
129        let mut query_params: HashMap<String, String> = HashMap::new();
130        if version::greater_than_or_equal_to(&self.version, "1.25") && force {
131            query_params.insert("force".into(), "1".into());
132        }
133        let query_params = if !query_params.is_empty() {
134            Some(query_params)
135        } else {
136            None
137        };
138
139        let request = Request::builder()
140            .method(Method::DELETE)
141            .uri(self.request_uri(
142                &format!(
143                    "/volumes/{}",
144                    utf8_percent_encode(volume_id, NON_ALPHANUMERIC).to_string()
145                ),
146                query_params,
147            )?)
148            .header("Accept", "application/json")
149            .body(Body::empty())
150            .context(HttpClientRequestBuilderError {})?;
151
152        let client = self.client.as_ref().unwrap();
153        let response = timeout(self.timeout, client.request(request))
154            .await
155            .context(HttpClientTimeoutError {})?
156            .context(HttpClientError {})?;
157        ensure!(
158            response.status().is_success(),
159            HttpClientResponseError {
160                status: response.status().as_u16()
161            }
162        );
163
164        Ok(())
165    }
166
167    /// volumes_prune requests the daemon to delete unused data.
168    pub async fn volumes_prune(
169        &self,
170        prune_filters: Option<Args>,
171    ) -> Result<VolumesPruneReport, Error> {
172        let mut query_params: HashMap<String, String> = HashMap::new();
173        if let Some(prune_filters) = prune_filters {
174            query_params.insert(
175                "filters".into(),
176                serde_json::to_string(&prune_filters.fields).context(JsonSerializationError {})?,
177            );
178        }
179        let query_params = if !query_params.is_empty() {
180            Some(query_params)
181        } else {
182            None
183        };
184
185        let request = Request::builder()
186            .method(Method::POST)
187            .uri(self.request_uri("/volumes/prune", query_params)?)
188            .header("Accept", "application/json")
189            .body(Body::empty())
190            .context(HttpClientRequestBuilderError {})?;
191
192        let client = self.client.as_ref().unwrap();
193        let response = timeout(self.timeout, client.request(request))
194            .await
195            .context(HttpClientTimeoutError {})?
196            .context(HttpClientError {})?;
197        ensure!(
198            response.status().is_success(),
199            HttpClientResponseError {
200                status: response.status().as_u16()
201            }
202        );
203        let response_body = read_response_body(response, self.timeout).await?;
204        Ok(serde_json::from_str(&response_body).context(JsonDeserializationError {})?)
205    }
206}
207
208#[cfg(test)]
209mod tests {
210    use crate::types::client::*;
211    use crate::types::filters::*;
212    use crate::{opts, LocalDockerEngineClient};
213    use maplit::hashmap;
214
215    #[tokio::test]
216    async fn test_volume_api() {
217        let docker_client =
218            LocalDockerEngineClient::new_client_with_opts(Some(vec![Box::new(opts::from_env)]))
219                .unwrap();
220
221        // Create a temporary named volume.
222        let name = "test_volume_api_volume";
223        let volume_create_response = docker_client
224            .volume_create(
225                VolumeCreateBodyBuilder::default()
226                    .name(Some(name.into()))
227                    .build()
228                    .unwrap(),
229            )
230            .await
231            .unwrap();
232
233        assert_eq!(volume_create_response.name.as_ref().unwrap(), name);
234        assert_eq!(volume_create_response.driver.as_ref().unwrap(), "local");
235
236        // Attempt to list the volume using a name based filter.
237        let volume_list_response = docker_client
238            .volume_list(Some(
239                ArgsBuilder::default()
240                    .fields(hashmap! {
241                        "name".into() => vec![name.into()],
242                    })
243                    .build()
244                    .unwrap(),
245            ))
246            .await
247            .unwrap();
248
249        let volumes = volume_list_response.volumes.unwrap();
250
251        assert_eq!(volumes.len(), 1);
252        assert_eq!(volumes[0].name.as_ref().unwrap(), name);
253
254        // Inspect the temporary volume directly.
255        let volume_inspect_response = docker_client.volume_inspect(name).await.unwrap();
256
257        assert_eq!(volume_inspect_response.name.as_ref().unwrap(), name);
258
259        // Remove and cleanup the temporary volume.
260        docker_client.volume_remove(name, true).await.unwrap();
261
262        // The temporary volume should no longer exist.
263        let response = docker_client.volume_inspect(name).await;
264
265        assert!(response.is_err());
266    }
267}