1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
use std::collections::HashMap;

use bollard::network::{CreateNetworkOptions, DisconnectNetworkOptions, ListNetworksOptions};
use tracing::{event, Level};

use crate::DockerTestError;

use super::Docker;

impl Docker {
    pub async fn create_singular_network_impl(
        &self,
        network_name: String,
    ) -> Result<String, DockerTestError> {
        let config = CreateNetworkOptions {
            name: network_name.as_str(),
            ..Default::default()
        };

        event!(Level::TRACE, "creating singular network");

        match self.client.create_network(config).await {
            Ok(resp) => match resp.id {
                Some(id) => Ok(id),
                None => Err(DockerTestError::Startup(
                    "failed to get id of singular network".to_string(),
                )),
            },
            Err(e) => {
                match e {
                    bollard::errors::Error::DockerResponseServerError {
                        status_code,
                        message,
                    } => {
                        if status_code == 409 {
                            // We assume that we got a conflict due to multiple networks with the name
                            // `dockertest`, and therefore assume that 'existing_dockertest_network' will
                            // return the conflicting network.
                            Ok(self
                                .existing_dockertest_network(&network_name)
                                .await?
                                .unwrap())
                        } else {
                            Err(DockerTestError::Startup(format!(
                                "failed to create singular network: {message}",
                            )))
                        }
                    }
                    _ => Err(DockerTestError::Startup(format!(
                        "failed to create singular network: {e}"
                    ))),
                }
            }
        }
    }

    pub async fn existing_dockertest_network(
        &self,
        network_name: &str,
    ) -> Result<Option<String>, DockerTestError> {
        let mut filter = HashMap::with_capacity(1);
        filter.insert("name", vec![network_name]);

        let opts = ListNetworksOptions { filters: filter };
        let networks = self
            .client
            .list_networks(Some(opts))
            .await
            .map_err(|e| DockerTestError::Startup(format!("failed to list networks: {e}")))?;

        let mut highest_timestamp: Option<String> = None;
        let mut highest_timestamp_id: Option<String> = None;

        // We assume id is present on the returned networks
        for n in networks {
            if let Some(name) = n.name {
                if name == network_name {
                    if let Some(timestamp) = n.created {
                        if let Some(compare_timestamp) = &highest_timestamp {
                            if timestamp.as_str() > compare_timestamp.as_str() {
                                highest_timestamp = Some(timestamp);
                                highest_timestamp_id = Some(n.id.unwrap());
                            }
                        } else {
                            highest_timestamp = Some(timestamp);
                            highest_timestamp_id = Some(n.id.unwrap());
                        }
                    }
                }
            }
        }

        Ok(highest_timestamp_id)
    }

    pub(crate) async fn create_network(
        &self,
        network_name: &str,
        self_container: Option<&str>,
    ) -> Result<(), DockerTestError> {
        let config = CreateNetworkOptions {
            name: network_name,
            ..Default::default()
        };

        event!(Level::TRACE, "creating network {}", network_name);
        let res = self
            .client
            .create_network(config)
            .await
            .map(|_| ())
            .map_err(|e| {
                DockerTestError::Startup(format!("creating docker network failed: {}", e))
            });

        event!(
            Level::TRACE,
            "finished created network with result: {}",
            res.is_ok()
        );

        if let Some(id) = self_container {
            if let Err(e) = self.add_self_to_network(id, network_name).await {
                if let Err(e) = self.client.remove_network(network_name).await {
                    event!(
                        Level::ERROR,
                        "unable to remove docker network `{}`: {}",
                        network_name,
                        e
                    );
                }
                return Err(e);
            }
        }

        res
    }

    /// Make sure we remove the network we have previously created.
    pub(crate) async fn delete_network(&self, network_name: &str, self_container: Option<&str>) {
        if let Some(id) = self_container {
            let opts = DisconnectNetworkOptions::<&str> {
                container: id,
                force: true,
            };
            if let Err(e) = self.client.disconnect_network(network_name, opts).await {
                event!(
                    Level::ERROR,
                    "unable to remove dockertest-container from network: {}",
                    e
                );
            }
        }

        if let Err(e) = self.client.remove_network(network_name).await {
            event!(
                Level::ERROR,
                "unable to remove docker network `{}`: {}",
                network_name,
                e
            );
        }
    }

    pub(crate) async fn add_self_to_network(
        &self,
        container_id: &str,
        network_name: &str,
    ) -> Result<(), DockerTestError> {
        event!(
            Level::TRACE,
            "adding dockertest container to created network, container_id: {}, network_id: {}",
            container_id,
            network_name,
        );
        let opts = bollard::network::ConnectNetworkOptions {
            container: container_id,
            endpoint_config: bollard::models::EndpointSettings::default(),
        };

        self.client
            .connect_network(network_name, opts)
            .await
            .map_err(|e| {
                DockerTestError::Startup(format!(
                    "failed to add internal container to dockertest network: {}",
                    e
                ))
            })
    }

    // TODO: isolate to static mod only
    pub async fn add_container_to_network(
        &self,
        container_id: &str,
        network: &str,
    ) -> Result<(), DockerTestError> {
        let opts = bollard::network::ConnectNetworkOptions {
            container: container_id,
            endpoint_config: bollard::models::EndpointSettings::default(),
        };

        event!(
            Level::DEBUG,
            "adding to network: {}, container: {}",
            network,
            container_id
        );

        match self.client.connect_network(network, opts).await {
            Ok(_) => Ok(()),
            Err(e) => match e {
                bollard::errors::Error::DockerResponseServerError {
                    status_code,
                    message: _,
                } => {
                    // The container was already connected to the network which is what we wanted
                    // anyway
                    if status_code == 403 {
                        Ok(())
                    } else {
                        Err(DockerTestError::Startup(format!(
                            "failed to add static container to dockertest network: {}",
                            e
                        )))
                    }
                }
                _ => Err(DockerTestError::Startup(format!(
                    "failed to add static container to dockertest network: {}",
                    e
                ))),
            },
        }
    }
}