docker_client_async/
network.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 network api implementation.
19
20use crate::error::*;
21use crate::types::client::{
22    NetworkConnect, NetworkCreateResponse, NetworkDisconnect, NetworkInspectOptions,
23    NetworkListOptions, NetworksPruneReport,
24};
25use crate::types::filters::Args;
26use crate::types::network::{
27    EndpointSettings, NetworkCreate, NetworkCreateBuilder, NetworkResource,
28};
29use crate::{read_response_body, DockerEngineClient};
30use hyper::client::connect::Connect;
31use hyper::{Body, Method, Request};
32use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
33use snafu::{ensure, ResultExt};
34use std::collections::HashMap;
35use tokio::time::timeout;
36
37impl<C: Connect + Clone + Send + Sync + 'static> DockerEngineClient<C> {
38    /// network_list returns the list of networks configured in the docker host.
39    pub async fn network_list(
40        &self,
41        options: Option<NetworkListOptions>,
42    ) -> Result<Vec<NetworkResource>, Error> {
43        let mut query_params: HashMap<String, String> = HashMap::new();
44        if let Some(options) = options {
45            if let Some(filter) = options.filters {
46                query_params.insert(
47                    "filters".into(),
48                    serde_json::to_string(&filter.fields).context(JsonSerializationError {})?,
49                );
50            }
51        }
52        let query_params = if !query_params.is_empty() {
53            Some(query_params)
54        } else {
55            None
56        };
57
58        let request = Request::builder()
59            .method(Method::GET)
60            .uri(self.request_uri("/networks", query_params)?)
61            .header("Accept", "application/json")
62            .body(Body::empty())
63            .context(HttpClientRequestBuilderError {})?;
64
65        let client = self.client.as_ref().unwrap();
66        let response = timeout(self.timeout, client.request(request))
67            .await
68            .context(HttpClientTimeoutError {})?
69            .context(HttpClientError {})?;
70        ensure!(
71            response.status().is_success(),
72            HttpClientResponseError {
73                status: response.status().as_u16()
74            }
75        );
76        let response_body = read_response_body(response, self.timeout).await?;
77        Ok(serde_json::from_str(&response_body).context(JsonDeserializationError {})?)
78    }
79
80    /// network_inspect returns the information for a specific network configured in the docker host.
81    pub async fn network_inspect(
82        &self,
83        network_id: &str,
84        options: Option<NetworkInspectOptions>,
85    ) -> Result<NetworkResource, Error> {
86        let mut query_params: HashMap<String, String> = HashMap::new();
87        if let Some(options) = options {
88            if options.verbose {
89                query_params.insert("verbose".into(), "true".into());
90            }
91            if let Some(scope) = options.scope {
92                query_params.insert("scope".into(), scope);
93            }
94        }
95        let query_params = if !query_params.is_empty() {
96            Some(query_params)
97        } else {
98            None
99        };
100
101        let request = Request::builder()
102            .method(Method::GET)
103            .uri(self.request_uri(
104                &format!(
105                    "/networks/{}",
106                    utf8_percent_encode(network_id, NON_ALPHANUMERIC).to_string()
107                ),
108                query_params,
109            )?)
110            .header("Accept", "application/json")
111            .body(Body::empty())
112            .context(HttpClientRequestBuilderError {})?;
113
114        let client = self.client.as_ref().unwrap();
115        let response = timeout(self.timeout, client.request(request))
116            .await
117            .context(HttpClientTimeoutError {})?
118            .context(HttpClientError {})?;
119        ensure!(
120            response.status().is_success(),
121            HttpClientResponseError {
122                status: response.status().as_u16()
123            }
124        );
125        let response_body = read_response_body(response, self.timeout).await?;
126        Ok(serde_json::from_str(&response_body).context(JsonDeserializationError {})?)
127    }
128
129    /// network_remove removes an existent network from the docker host.
130    pub async fn network_remove(&self, network_id: &str) -> Result<(), Error> {
131        let request = Request::builder()
132            .method(Method::DELETE)
133            .uri(self.request_uri(
134                &format!(
135                    "/networks/{}",
136                    utf8_percent_encode(network_id, NON_ALPHANUMERIC).to_string()
137                ),
138                None,
139            )?)
140            .header("Accept", "application/json")
141            .body(Body::empty())
142            .context(HttpClientRequestBuilderError {})?;
143
144        let client = self.client.as_ref().unwrap();
145        let response = timeout(self.timeout, client.request(request))
146            .await
147            .context(HttpClientTimeoutError {})?
148            .context(HttpClientError {})?;
149        ensure!(
150            response.status().is_success(),
151            HttpClientResponseError {
152                status: response.status().as_u16()
153            }
154        );
155
156        Ok(())
157    }
158
159    /// network_create creates a new network in the docker host.
160    pub async fn network_create(
161        &self,
162        name: &str,
163        options: Option<NetworkCreate>,
164    ) -> Result<NetworkCreateResponse, Error> {
165        let mut network_create_request =
166            options.unwrap_or_else(|| NetworkCreateBuilder::default().build().unwrap());
167        network_create_request.name = Some(name.into());
168
169        let request = Request::builder()
170            .method(Method::POST)
171            .uri(self.request_uri("/networks/create", None)?)
172            .header("Content-Type", "application/json")
173            .header("Accept", "application/json")
174            .body(Body::from(
175                serde_json::to_string(&network_create_request)
176                    .context(JsonSerializationError {})?,
177            ))
178            .context(HttpClientRequestBuilderError {})?;
179
180        let client = self.client.as_ref().unwrap();
181        let response = timeout(self.timeout, client.request(request))
182            .await
183            .context(HttpClientTimeoutError {})?
184            .context(HttpClientError {})?;
185        ensure!(
186            response.status().is_success(),
187            HttpClientResponseError {
188                status: response.status().as_u16()
189            }
190        );
191        let response_body = read_response_body(response, self.timeout).await?;
192        Ok(serde_json::from_str(&response_body).context(JsonDeserializationError {})?)
193    }
194
195    /// network_connect connects a container to an existent network in the docker host.
196    pub async fn network_connect(
197        &self,
198        network_id: &str,
199        container_id: &str,
200        config: Option<EndpointSettings>,
201    ) -> Result<(), Error> {
202        let network_connect_request = NetworkConnect {
203            container: container_id.into(),
204            endpoint_config: config,
205        };
206
207        let request = Request::builder()
208            .method(Method::POST)
209            .uri(self.request_uri(
210                &format!(
211                    "/networks/{}/connect",
212                    utf8_percent_encode(network_id, NON_ALPHANUMERIC).to_string()
213                ),
214                None,
215            )?)
216            .header("Content-Type", "application/json")
217            .header("Accept", "application/json")
218            .body(Body::from(
219                serde_json::to_string(&network_connect_request)
220                    .context(JsonSerializationError {})?,
221            ))
222            .context(HttpClientRequestBuilderError {})?;
223
224        let client = self.client.as_ref().unwrap();
225        let response = timeout(self.timeout, client.request(request))
226            .await
227            .context(HttpClientTimeoutError {})?
228            .context(HttpClientError {})?;
229        ensure!(
230            response.status().is_success(),
231            HttpClientResponseError {
232                status: response.status().as_u16()
233            }
234        );
235
236        Ok(())
237    }
238
239    /// network_disconnect disconnects a container from an existent network in the docker host.
240    pub async fn network_disconnect(
241        &self,
242        network_id: &str,
243        container_id: &str,
244        force: bool,
245    ) -> Result<(), Error> {
246        let network_disconnect_request = NetworkDisconnect {
247            container: container_id.into(),
248            force: if force { Some(true) } else { None },
249        };
250
251        let request = Request::builder()
252            .method(Method::POST)
253            .uri(self.request_uri(
254                &format!(
255                    "/networks/{}/disconnect",
256                    utf8_percent_encode(network_id, NON_ALPHANUMERIC).to_string()
257                ),
258                None,
259            )?)
260            .header("Content-Type", "application/json")
261            .header("Accept", "application/json")
262            .body(Body::from(
263                serde_json::to_string(&network_disconnect_request)
264                    .context(JsonSerializationError {})?,
265            ))
266            .context(HttpClientRequestBuilderError {})?;
267
268        let client = self.client.as_ref().unwrap();
269        let response = timeout(self.timeout, client.request(request))
270            .await
271            .context(HttpClientTimeoutError {})?
272            .context(HttpClientError {})?;
273        ensure!(
274            response.status().is_success(),
275            HttpClientResponseError {
276                status: response.status().as_u16()
277            }
278        );
279
280        Ok(())
281    }
282
283    /// networks_prune requests the daemon to delete unused networks.
284    pub async fn networks_prune(
285        &self,
286        prune_filters: Option<Args>,
287    ) -> Result<NetworksPruneReport, Error> {
288        let mut query_params: HashMap<String, String> = HashMap::new();
289        if let Some(prune_filters) = prune_filters {
290            query_params.insert(
291                "filters".into(),
292                serde_json::to_string(&prune_filters.fields).context(JsonSerializationError {})?,
293            );
294        }
295        let query_params = if !query_params.is_empty() {
296            Some(query_params)
297        } else {
298            None
299        };
300
301        let request = Request::builder()
302            .method(Method::POST)
303            .uri(self.request_uri("/networks/prune", query_params)?)
304            .header("Accept", "application/json")
305            .body(Body::empty())
306            .context(HttpClientRequestBuilderError {})?;
307
308        let client = self.client.as_ref().unwrap();
309        let response = timeout(self.timeout, client.request(request))
310            .await
311            .context(HttpClientTimeoutError {})?
312            .context(HttpClientError {})?;
313        ensure!(
314            response.status().is_success(),
315            HttpClientResponseError {
316                status: response.status().as_u16()
317            }
318        );
319        let response_body = read_response_body(response, self.timeout).await?;
320        Ok(serde_json::from_str(&response_body).context(JsonDeserializationError {})?)
321    }
322}
323
324#[cfg(test)]
325mod tests {
326    use crate::types::client::*;
327    use crate::types::container::ContainerConfigBuilder;
328    use crate::types::filters::*;
329    use crate::{opts, LocalDockerEngineClient};
330    use maplit::hashmap;
331
332    #[tokio::test]
333    async fn test_network_api() {
334        let docker_client =
335            LocalDockerEngineClient::new_client_with_opts(Some(vec![Box::new(opts::from_env)]))
336                .unwrap();
337
338        // Create a new network for this test.
339        let name = "test_network";
340        let network_create_response = docker_client.network_create(name, None).await.unwrap();
341
342        // List networks (filtering for the id of the test network).
343        let networks_response = docker_client
344            .network_list(Some(
345                NetworkListOptionsBuilder::default()
346                    .filters(Some(
347                        ArgsBuilder::default()
348                            .fields(hashmap! {
349                                "id".into() => vec![network_create_response.id.clone()],
350                            })
351                            .build()
352                            .unwrap(),
353                    ))
354                    .build()
355                    .unwrap(),
356            ))
357            .await
358            .unwrap();
359
360        assert_eq!(networks_response.len(), 1);
361        assert_eq!(networks_response[0].name.as_ref().unwrap(), name);
362
363        // Inspect the test network, in verbose mode.
364        let network_inspect_response = docker_client
365            .network_inspect(
366                &network_create_response.id,
367                Some(
368                    NetworkInspectOptionsBuilder::default()
369                        .verbose(true)
370                        .build()
371                        .unwrap(),
372                ),
373            )
374            .await
375            .unwrap();
376
377        assert_eq!(network_inspect_response.name.as_ref().unwrap(), name);
378
379        // Create an ephemeral container for network connection testing.
380        let container_create_response = docker_client
381            .container_create(
382                ContainerConfigBuilder::default()
383                    .image(Some("busybox".into()))
384                    .cmd(Some(vec!["sleep".into(), "60".into()]))
385                    .build()
386                    .unwrap(),
387                Some("test_network_api_container".into()),
388            )
389            .await
390            .unwrap();
391
392        // Connect the test network to the test container.
393        docker_client
394            .network_connect(
395                &network_create_response.id,
396                &container_create_response.id,
397                None,
398            )
399            .await
400            .unwrap();
401
402        // Inspect the ephemeral container.
403        let container_inspect_response = docker_client
404            .container_inspect(&container_create_response.id)
405            .await
406            .unwrap();
407
408        // The ephemeral container should be connected to the test network.
409        assert!(container_inspect_response
410            .network_settings
411            .as_ref()
412            .unwrap()
413            .networks
414            .as_ref()
415            .unwrap()
416            .contains_key(name));
417
418        // Disconnect the ephemeral container from the test network.
419        docker_client
420            .network_disconnect(
421                &network_create_response.id,
422                &container_create_response.id,
423                true,
424            )
425            .await
426            .unwrap();
427
428        // Inspect the ephemeral container.
429        let container_inspect_response = docker_client
430            .container_inspect(&container_create_response.id)
431            .await
432            .unwrap();
433
434        // Ephemeral container should now be disconnected from the test network.
435        assert!(!container_inspect_response
436            .network_settings
437            .as_ref()
438            .unwrap()
439            .networks
440            .as_ref()
441            .unwrap()
442            .contains_key(name));
443
444        // Cleanup the ephemeral container.
445        docker_client
446            .container_remove(
447                &container_create_response.id,
448                Some(
449                    ContainerRemoveOptionsBuilder::default()
450                        .force(true)
451                        .build()
452                        .unwrap(),
453                ),
454            )
455            .await
456            .unwrap();
457
458        // Cleanup the test network.
459        docker_client
460            .network_remove(&network_create_response.id)
461            .await
462            .unwrap();
463
464        // Ensure the test network was removed by querying for its existence.
465        let response = docker_client
466            .network_inspect(
467                &network_create_response.id,
468                Some(
469                    NetworkInspectOptionsBuilder::default()
470                        .verbose(true)
471                        .build()
472                        .unwrap(),
473                ),
474            )
475            .await;
476
477        assert!(response.is_err());
478    }
479}